1 Packages

As usual, we will need to load some packages.

Load the tidyverse and DESeq2 packages, install them if they are not already.

require("tidyverse")
require("DESeq2")

Now, load the DESeq2 object that you saved previously using the readRDS function.

Load the Rds object containing the DESeq2 object into the “dds” variable

dds <- readRDS("./dds.Rds")

While we already applied some filtering the to count matrix that we used as an input, we can still pre-filter the dataset further. You can, in fact, treat the DESeqDataSet object (in our dds variable) as a matrix to subset it.

For example, dim(dds) will return the dimensions of the matrix (number of features or genes, and number of samples). Similarly, we can subset to the first 3 samples and first 10 genes with dds[1:10,1:3].

To access the full count matrix, you can use counts(dds), and to access the sample metadata, you use colData(dds).

2 Some more prefiltering

2.1 Pre-filtering genes

It is customary to remove genes that have very low expression, since their signal-to-noise ratio is very small to yield any biologically significant results.

Keep in the dds object only genes with at least 10 reads in more than 25% of the samples

Useful functions:

  • rowSums()
nrow(dds)
#> [1] 20180
nSamples <- ncol(dds)*.25
genes.keep <- rowSums(counts(dds) >=10) > nSamples
dds <- dds[genes.keep,]
nrow(dds)
#> [1] 17399

We went from about 20,200 genes down to about 17,400.

2.2 Sample characterization

Now we want to explore the samples in our dataset. In order to compare the columns of our matrix, however, we need to normalize the counts. The simplest way would be to take the sum of each column (i.e., the total library size of each sample) and divide the counts by the value. The DESeq2 package offers some more sophisticated methods, and we will use the “variance stabilising transformation” in this tutorial (the vst() function). The output is a DESeqTransform object, and the values calculated can be extracted from it using the assay() function:

norm_expr <- vst(dds) %>% assay

Execute the command above to obtain a matrix of normalized gene expression

norm_expr <- vst(dds) %>% assay

Before we plot the expression values we just calculated, let’s install and load the pheatmap package, used to plot heatmaps.

require("pheatmap")

Now, instead of plotting the whole normalized expression matrix that we obtained using the vst() function, let us only plot the top 1,000 most varying genes using the pheatmap() function.

Calculate the standard deviation for each gene and pick the top 1,000 genes with the highest. Plot the vst expression matrix for those genes using the pheatmap() function.

Useful functions:

  • apply()
  • sd()
  • rowSds()
  • arrange()
  • desc()
  • slice_max()
  • pheatmap()
plot.genes <- tibble(gene_name=rownames(norm_expr), sd=norm_expr %>% rowSds ) %>%
    slice_max(sd, n=1000) %>%
    pull(gene_name)
norm_expr[plot.genes, ] %>% pheatmap()

The heatmap is not that informative to compare across samples, because it only shows that some genes have a higher level of expression than others. By default, pheatmap() does not scale the data, but we can do that using scale=“row” or scale=“column”.

Plot the heatmap again but this time using scale=“row”

norm_expr[plot.genes, ] %>%
    pheatmap(scale="row")

What could the two clusters be representing? We can annotate the columns of the heatmap by using the option “annotation_col” of pheatmap(). We need to provide a data.frame with the variables to annotate. For example:

annotation_col=as.data.frame(colData(dds)[,c("condition","origin")])

Plot the same heatmap but adding annotations for the condition, the origin, the RIN values and the post-mortem time

norm_expr[plot.genes, ] %>%
    pheatmap(scale="row", annotation_col=as.data.frame(colData(dds)[,c("condition","origin", "rin", "pm_time_min")]))

The fact that the two cohorts are so different is a reason for concern. We know that the cohorts have very different RIN values and post-mortem intervals, and gene expression may reflect those differences.

2.2.1 Pairwise correlation between samples

We are going to calculate how different each pair of samples are, and then summarize each sample by its median difference to the rest of the samples.

The cor() function calculates the correlation between each pair of columns of the input matrix, and we can take advantage of it.

Use the cor() function on the subset of 1,000 most varying genes from the vst transformed gene expression data to calculate all pairwise correlations and plot the resulting correlation matrix using pheatmap().

Useful functions:

  • cor()
  • pheatmap()
Cors <- cor(norm_expr[plot.genes, ])
pheatmap(Cors)

Calculate the median correlation per sample.

Useful functions:

  • rowMedians()
  • sort()
medCors <- rowMedians(Cors)
names(medCors) <- rownames(Cors)
medCors %>% sort
#>  SL283585  SL283597  SL283590  SL283575  SL283616  SL283618  SL283570  SL283581 
#> 0.7567623 0.7572315 0.7606560 0.7871291 0.8066868 0.8169079 0.8392480 0.8453424 
#>  SL283566  SL283584  SL283576  SL283610  SL283599  SL283595  SL283596  SL283614 
#> 0.8495857 0.8548754 0.8627927 0.8654529 0.8686231 0.8698628 0.8741854 0.8835448 
#>  SL283587  SL283583  SL283617  SL283589  SL283592  SL283565  SL283605  SL283608 
#> 0.8841093 0.8846946 0.8890795 0.8904316 0.8909524 0.8911854 0.8920549 0.8924803 
#>  SL283586  SL283598  SL283600  SL283604  SL283603  SL283568  SL283593  SL283569 
#> 0.8941451 0.8952702 0.8963120 0.8989816 0.9008564 0.9013622 0.9015567 0.9016811 
#>  SL283601  SL283612  SL283572  SL283574  SL283606  SL283602  SL283578  SL283580 
#> 0.9024367 0.9046887 0.9079857 0.9086827 0.9087302 0.9100107 0.9100342 0.9122101 
#>  SL283591  SL283588  SL283579  SL283567  SL283577  SL283573  SL283594  SL283582 
#> 0.9133564 0.9152114 0.9152738 0.9155428 0.9178124 0.9234158 0.9240418 0.9274764 
#>  SL283571 
#> 0.9275740

A straightforward way to display outliers in this case is to plot a boxplot with ggplot2, which by default plots outliers as points:

ggplot(tibble(medCors)) + geom_boxplot(aes(x=1, y=medCors))

Plot a ggplot2 boxplot using the command above.

ggplot(tibble(medCors)) + geom_boxplot(aes(x=1, y=medCors))

We can see that four samples (SL283585, SL283597, SL283590, SL283575) are shown as outliers - they are quite different from all the rest.

Now we will plot all the samples in the first 2 principal components of a PCA. The easiest is probably to use the plotPCA() function from the DESeq2 package, which works on the result from vst(). Note that you have to run vst() again on the dds object, since plotPCA() works on its output.

Useful functions:

  • vst()
  • plotPCA()

Use the plotPCA() function to visualize the samples in the first 2 principal components of the expression data.

plotPCA(vst(dds))

Do the same, but for each cohort (origin) separately.

plotPCA(vst(dds)[,colData(dds)$origin=="PW"])

plotPCA(vst(dds)[,colData(dds)$origin=="NBB"])

Can you spot the two clusters in the NBB cohort?

The plotPCA() function from the DESeq2 package does something sneaky behind the scenes. If you check the help for the function, you’ll see that the argument “ntop” is set to 500 by default. To save time, the function carries out the PCA only on the top 500 most varying genes, similarly to what we were doing before (but using the top 1,000). What happens if we use, for example, only the top 100 instead?

Use plotPCA() with ntop=400, then ntop=200, and finally with ntop=10 for all samples.

plotPCA(vst(dds), ntop=400)

plotPCA(vst(dds), ntop=200)

plotPCA(vst(dds), ntop=10)

The fewer genes we use to construct the PCA, the more obvious the clusters become. What is going on? What could it be that separates our samples in two groups? Let’s have a look at this top 10 most varying genes.

First, load the gene descriptions that we used in the first tutorial using

geneNames <- readRDS(url("https://git.app.uib.no/neuromics/cell-composition-rna-pd/-/raw/master/Data/EnsDb.Hsapiens.v75.Rds"))

Load the geneNames.Rds file into the R session and convert it to a tibble

geneNames <- readRDS(url("https://git.app.uib.no/neuromics/cell-composition-rna-pd/-/raw/master/Data/EnsDb.Hsapiens.v75.Rds")) %>%
    as_tibble

Then, find the gene names of the top10 most varying genes in the geneNames object.

Find the top10 most varying genes in the geneNames object.

tibble(gene_name=rownames(norm_expr), sd=norm_expr %>% rowSds ) %>%
    slice_max(sd, n=10) %>%
    left_join(geneNames) %>%
    select(gene_name, seqnames) %>%
    unique

In which chromosome are most of these genes located? Can it explain the groups you obseved in the PCA?

Plot the PCA again using the plotPCA() function using ntop=10 and intgroup=“sex”.

plotPCA(vst(dds), ntop=10, intgroup="sex")

Although sex-associated gene expression is not exclusively restricted to sex chromosomes, it is of a much greater magnitude in the sex chromosomes.

2.3 Check sex assignment

We can use the strong association between sex-chromosome genes and sex to assess potential mis-labeling of samples. A quick way is to use all genes located in the Y chromosome to run a PCA.

Plot all the samples in the two principal components of the PCA of the Y-located genes, colouring the points by the sex of the sample. NOTE: use the vst() function on the whole dataset, subset after.

Ygenes <- geneNames %>% filter(seqnames == "Y", gene_biotype == "protein_coding") %>%
    pull(gene_name) %>% unique
vst(dds)[rownames(dds) %in% Ygenes,] %>% plotPCA(ntop=Inf, intgroup="sex")

As you can see, most of the variance is explained by the first principal component, as expected. It would in fact be enough to plot the values for the PC1.

correct_sign <- function(x) {
    if (median(x)<0) return(-1*x)
    else return(x)
}
pca1 <- vst(dds)[rownames(dds) %in% Ygenes,] %>% 
    assay %>%
    t %>%
    prcomp
pca1$x %>%
    as_tibble(rownames="sample_id") %>%
    select(sample_id, PC1) %>%
    mutate(PC1=correct_sign(PC1)) %>%
    left_join(as_tibble(colData(dds))) %>%
    ggplot(aes(x=sex, y=PC1)) +
    geom_boxplot(outlier.shape=NA) +
    geom_jitter(aes(colour=sex), height=0, width=.2)
correct_sign <- function(x) {
    if (median(x)<0) return(-1*x)
    else return(x)
}
pca1 <- vst(dds)[rownames(dds) %in% Ygenes,] %>% 
    assay %>%
    t %>%
    prcomp
pca1$x %>%
    as_tibble(rownames="sample_id") %>%
    select(sample_id, PC1) %>%
    mutate(PC1=correct_sign(PC1)) %>%
    left_join(as_tibble(colData(dds))) %>%
    ggplot(aes(x=sex, y=PC1)) +
    geom_boxplot(outlier.shape=NA) +
    geom_jitter(aes(colour=sex), height=0, width=.2)

For simplicity, we are going to remove from the dds object all genes mapped to the sex chromosomes.

Remove all X and Y genes from the dds object.

sex.genes <- geneNames %>% filter(seqnames %in% c("X","Y")) %>%
    pull(gene_name) %>% unique
dim(dds)
#> [1] 17399    49
dds <- dds[!rownames(dds) %in% sex.genes,]
dim(dds)
#> [1] 16755    49

Recalculate the pairwise correlations between samples as before with the filtered dataset. Plot the values and plot the PCA with ntop=1000

norm_expr <- vst(dds) %>% assay
top1000.genes <- tibble(gene_name=rownames(norm_expr), sd=norm_expr %>% rowSds ) %>%
    slice_max(sd, n=1000) %>%
    pull(gene_name)
Cors <- cor(norm_expr[top1000.genes, ])
medCors <- rowMedians(Cors)
names(medCors) <- rownames(Cors)
medCors %>% sort
#>  SL283597  SL283585  SL283590  SL283575  SL283616  SL283618  SL283581  SL283570 
#> 0.7627503 0.7682360 0.7782123 0.7977671 0.8229228 0.8239435 0.8447489 0.8559964 
#>  SL283566  SL283584  SL283576  SL283599  SL283610  SL283596  SL283595  SL283583 
#> 0.8576488 0.8708081 0.8778189 0.8798999 0.8831353 0.8856723 0.8859760 0.8899975 
#>  SL283614  SL283592  SL283587  SL283617  SL283565  SL283603  SL283608  SL283589 
#> 0.8914506 0.8977170 0.8985736 0.9024982 0.9032124 0.9049152 0.9060812 0.9098299 
#>  SL283605  SL283568  SL283604  SL283593  SL283586  SL283598  SL283600  SL283612 
#> 0.9100418 0.9114346 0.9129014 0.9129787 0.9129787 0.9136635 0.9140187 0.9146281 
#>  SL283602  SL283578  SL283601  SL283574  SL283606  SL283572  SL283569  SL283591 
#> 0.9175766 0.9194735 0.9209084 0.9209463 0.9216333 0.9224393 0.9234645 0.9243526 
#>  SL283580  SL283567  SL283579  SL283588  SL283594  SL283577  SL283573  SL283582 
#> 0.9244825 0.9249829 0.9256202 0.9308943 0.9318754 0.9344447 0.9352384 0.9364574 
#>  SL283571 
#> 0.9429330
ggplot(tibble(medCors)) + geom_boxplot(aes(x=1, y=medCors))

plotPCA(vst(dds))

In order to plot the sample ids, we need to use the “returnData=TRUE” option, in the plotPCA() function which, instead of plotting, will then returns the data in a format that can be easily used with ggplot2, adding a layer with the labels:

ggplot(data) +
geom_text(aes(label=name), nudge_y=1.5, size=3)

We could also, of course, run the PCA ourselves using prcomp().

plotPCA(vst(dds), returnData=TRUE) %>%
  ggplot(aes(x=PC1, y=PC2)) + 
  geom_point(aes(colour=condition)) +
  geom_text(aes(label=name), nudge_y=1.5, size=3)

Evaluating the PCA plot and the heatmap of correlations, do you think it is justified to remove any of the samples?

3 Cell type estimation

Before proceeding with the differential expression analysis, since we are dealing with human brain tissue, we need to investigate what the cell composition is in our samples. This is of fundamental importance in these type of datasets because most of the variability in gene expression in explained by the cell types.

We will use a very simple approach to estimate cell types similar, in a way, to what we used to “estimate” sex: summarize gene expression of a set of selected markers as their first principal component (PC1). The marker genes will be known cell type-specific genes.

Let’s load a list of markers of cortical cell types from Neuroespresso:

cortical.markers <- read_tsv("./Data/cortical_markers.tsv")

Read the tab-separated file “cortical_markers.tsv” into a tibble

cortical.markers <- read_tsv("./Data/cortical_markers.tsv")

Now let us run a PCA (as we have done until now) but subsetting the genes to the neuronal markers. We will use the prcomp() function and extract the values of the PC1 for each sample.

Run a PCA using prcomp() only on neuronal markers. Explore the result and find where the coordinates for the first PC are.

estimate_ct <- function(object, genes, ct="PC1", rescale=TRUE) {
    object.vst <- vst(object)
    pca <- object.vst[rownames(object.vst) %in% genes,] %>%
        assay() %>%
        t() %>%
        prcomp()
    result <- as_tibble(pca$x, rownames="sample_id") %>%
        select(sample_id, PC1) %>%
        mutate(PC1=correct_sign(PC1))
    colnames(result) <- c("sample_id", ct)
    if (rescale) result[,ct] <- scale(result[,ct])[,1]
    return(result)
}
neuronal.est <- estimate_ct(dds, cortical.markers %>% filter(cell_type=="Neuron") %>% pull(gene_name), ct="Neuron")

Now we can do that for every other cortical cell type.

Calculate the first principal component for the other cell types and add them all to the metadatata of the dds object (colData(dds)).

ct.est <- lapply(unique(cortical.markers$cell_type), function(ct){
    message(paste0(ct, "..."))
    Genes <- filter(cortical.markers, cell_type==ct) %>% pull(gene_name)
    estimate_ct(dds, genes=Genes, ct=ct)
}) %>% Reduce("left_join", .)

newColData <- left_join(as_tibble(colData(dds)), ct.est, by="sample_id") %>%
    as("DataFrame")
rownames(newColData) <- newColData$sample_id
all(rownames(colData(dds)) == rownames(newColData))
#> [1] TRUE
colData(dds) <- newColData

And finally, do the same with the RNA markers for the cortical synapsome, which are not quite the same as the set of neuronal markers. These are RNAs found in the synapses, obtained from Hafner et al. 2019.

synaptosome.markers <- read_tsv("./Data/synaptosome_markers.tsv")
#syn <- qusage::read.gmt("./Data/synaptosome_symbols.gmt") %>% qdapTools::list2df() %>% as_tibble
#colnames(syn) <- c("gene_id", "cell_type")
#write_tsv(syn, "./Data/synaptosome_markers.tsv")
synaptosome.markers <- read_tsv("./Data/synaptosome_markers.tsv")

syn.est <- estimate_ct(dds, genes=unique(synaptosome.markers$gene_id), ct="synaptosome")
all(syn.est$sample_id == colData(dds)$sample_id)
#> [1] TRUE
colData(dds)$Synapses <- syn.est$synaptosome
colData(dds)
#> DataFrame with 49 rows and 16 columns
#>            sample_id      origin age_years         sex pm_time_min condition
#>          <character> <character> <numeric> <character>   <numeric>  <factor>
#> SL283595    SL283595          PW        79           M        2880   Control
#> SL283593    SL283593          PW        85           F        4320   Control
#> SL283618    SL283618          PW        88           F        3000   Control
#> SL283601    SL283601          PW        65           M        2880   Control
#> SL283565    SL283565         NBB        85           F         425   Control
#> ...              ...         ...       ...         ...         ...       ...
#> SL283592    SL283592          PW        69           M        3420      Case
#> SL283588    SL283588          PW        72           M        2880      Case
#> SL283600    SL283600          PW        78           F        1440      Case
#> SL283594    SL283594          PW        82           M        5040      Case
#> SL283590    SL283590          PW        95           M        2880      Case
#>                rin       Batch sample_id_paper  Astrocyte Endothelial Microglia
#>          <numeric> <character>     <character>  <numeric>   <numeric> <numeric>
#> SL283595       3.4           2           Ctr-1  -1.184423   -1.160479  0.955535
#> SL283593       5.9           2          Ctr-10  -0.309941    0.537182  0.469155
#> SL283618       5.6           4          Ctr-11   0.217133   -1.489182 -2.429756
#> SL283601       4.9           3          Ctr-14  -0.498226    0.213325  0.744386
#> SL283565       6.6           3          Ctr-23   0.153747    0.319363 -0.260804
#> ...            ...         ...             ...        ...         ...       ...
#> SL283592       4.5           2            PD-5 -0.8294955   -0.938985  0.201632
#> SL283588       5.6           4            PD-6 -0.0199344    0.681234  0.483412
#> SL283600       6.2           3            PD-7 -1.0950377   -0.521545  1.167569
#> SL283594       6.1           1            PD-8  0.6990216    0.729143  0.121045
#> SL283590       5.9           2            PD-9 -2.7878270   -2.481439  1.771681
#>               Oligo OligoPrecursors     Neuron  Synapses
#>           <numeric>       <numeric>  <numeric> <numeric>
#> SL283595   0.782419      -0.6619144  -0.705792 -0.902350
#> SL283593   0.532277       0.0857936   0.630432  0.461680
#> SL283618  -0.160397      -1.1894146  -0.866282 -1.078199
#> SL283601   0.244944      -0.3442277  -0.264953 -0.349270
#> SL283565  -2.747786      -0.1931905   0.123844  0.269135
#> ...             ...             ...        ...       ...
#> SL283592 -1.5972658       -1.140781 -0.4162497 -0.652125
#> SL283588  0.0917733       -0.213461  0.0183912 -0.155062
#> SL283600 -1.1012333       -0.734881 -0.6989150 -0.817925
#> SL283594 -0.2983306        0.625531  0.8981723  0.721039
#> SL283590 -0.1372109       -1.721758 -2.5800732 -2.100692

How can we assess which variables are responsible for most of the variation in gene expression? PCA is very helpful in this regards. By definition, the first principal component will maximize the variance that it explains by combining linearly the variables. In a way, it is “summarizing” gene expression by optimally projecting it into one dimension. Then, with the leftover variance not explained by PC1, it will do the same, but with the constaint that PC2 needs to be perpendicular to PC1.

The first principal components are often referred to as the main lines of variation, because they gather as much as possible, and often serve as a good “summary” of the multidimensional data.

If we want to assess whether cell type composition (or other variables) explain most of the variation, a simple way could be to test for association between variables and PCs. This is a bit involved programmatically, so here is the code:

scale.vec <- function(x) scale(x)[,1]

pca2 <- dds %>% 
    vst %>%
    assay %>%
    t %>%
    prcomp

Metadata.pca <- pca2$x %>%
    as_tibble(rownames="sample_id") %>%
    select(sample_id, PC1:PC5) %>%
    mutate_if(is.numeric, scale.vec) %>%
    left_join(as_tibble(colData(dds)))

Metadata.pca <- Metadata.pca %>% select(-sample_id, -origin, -sample_id_paper) %>% mutate_if(is.character, as.factor) %>%
    mutate_if(is.factor, as.integer)

allCors <- lapply(paste0("PC", 1:5), function(pc) {
    lapply(colnames(Metadata.pca)[6:ncol(Metadata.pca)], function(var){
        #message(paste0(pc, " vs ", var))
        lm(formula=as.formula(paste0(pc, "~", var)), data=Metadata.pca) %>%
            broom::tidy() %>% filter(term!="(Intercept)") %>%
            mutate(PC=pc, VAR=var)
    }) %>% Reduce("bind_rows",.)
}) %>% Reduce("bind_rows",.)

Pvals <- allCors %>% select(p.value, PC, VAR) %>%
    pivot_wider(names_from=VAR, values_from=p.value)
p.mat <- as.matrix(Pvals[,-1])
rownames(p.mat) <- Pvals$PC

Estimates <- allCors %>% select(estimate, PC, VAR) %>%
    pivot_wider(names_from=VAR, values_from=estimate)
e.mat <- as.matrix(Estimates[,-1])
rownames(e.mat) <- Estimates$PC

corrplot::corrplot(e.mat, p.mat=p.mat)
scale.vec <- function(x) scale(x)[,1]

pca2 <- dds %>% 
    vst %>%
    assay %>%
    t %>%
    prcomp

Metadata.pca <- pca2$x %>%
    as_tibble(rownames="sample_id") %>%
    select(sample_id, PC1:PC5) %>%
    mutate_if(is.numeric, scale.vec) %>%
    left_join(as_tibble(colData(dds)))

Metadata.pca <- Metadata.pca %>% select(-sample_id, -origin, -sample_id_paper) %>% mutate_if(is.character, as.factor) %>%
    mutate_if(is.factor, as.integer)

allCors <- lapply(paste0("PC", 1:5), function(pc) {
    lapply(colnames(Metadata.pca)[6:ncol(Metadata.pca)], function(var){
        #message(paste0(pc, " vs ", var))
        lm(formula=as.formula(paste0(pc, "~", var)), data=Metadata.pca) %>%
            broom::tidy() %>% filter(term!="(Intercept)") %>%
            mutate(PC=pc, VAR=var)
    }) %>% Reduce("bind_rows",.)
}) %>% Reduce("bind_rows",.)

Pvals <- allCors %>% select(p.value, PC, VAR) %>%
    pivot_wider(names_from=VAR, values_from=p.value)
p.mat <- as.matrix(Pvals[,-1])
rownames(p.mat) <- Pvals$PC

Estimates <- allCors %>% select(estimate, PC, VAR) %>%
    pivot_wider(names_from=VAR, values_from=estimate)
e.mat <- as.matrix(Estimates[,-1])
rownames(e.mat) <- Estimates$PC

corrplot::corrplot(e.mat, p.mat=p.mat)

Do you see anything interesting? May it be that the RNA quality (RIN) is associated with cell type composition? That can easily be tested with a quick linear regression: try predicting the RIN values with the estimated values for synaptosomal content, then try the same with the estimated neuronal content, and then with both at the same time in the model. The formulas are as follow:

  • rin ~ Synapses
  • rin ~ Neuron
  • rin ~ Synapses + Neuron

Then compare the resulting models using the summary() function on the linear fits.

Run 3 linear regressions with rin as a response variable and 1. Synapses; 2. Neuron; and 3. Synapses + Neuron as predictors. Check the adjusted R-squared values to compare the models.

# RIN
lm.syn <- lm(rin~Synapses, data=as.data.frame(colData(dds)))
lm.neu <- lm(rin~Neuron, data=as.data.frame(colData(dds)))
lm.both <- lm(rin~Neuron+Synapses, data=as.data.frame(colData(dds)))
lm.both.cohort <- lm(rin~Neuron+Synapses+origin, data=as.data.frame(colData(dds)))
lm.syn %>% summary
#> 
#> Call:
#> lm(formula = rin ~ Synapses, data = as.data.frame(colData(dds)))
#> 
#> Residuals:
#>     Min      1Q  Median      3Q     Max 
#> -1.5931 -0.7244  0.0062  0.6431  2.4892 
#> 
#> Coefficients:
#>             Estimate Std. Error t value Pr(>|t|)    
#> (Intercept)   5.9490     0.1348  44.141  < 2e-16 ***
#> Synapses      1.2082     0.1362   8.873 1.31e-11 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 0.9434 on 47 degrees of freedom
#> Multiple R-squared:  0.6262, Adjusted R-squared:  0.6182 
#> F-statistic: 78.73 on 1 and 47 DF,  p-value: 1.31e-11
lm.neu %>% summary
#> 
#> Call:
#> lm(formula = rin ~ Neuron, data = as.data.frame(colData(dds)))
#> 
#> Residuals:
#>      Min       1Q   Median       3Q      Max 
#> -1.88488 -0.93143 -0.09251  0.55718  2.74936 
#> 
#> Coefficients:
#>             Estimate Std. Error t value Pr(>|t|)    
#> (Intercept)   5.9490     0.1552  38.343  < 2e-16 ***
#> Neuron        1.0846     0.1568   6.919 1.09e-08 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 1.086 on 47 degrees of freedom
#> Multiple R-squared:  0.5046, Adjusted R-squared:  0.494 
#> F-statistic: 47.87 on 1 and 47 DF,  p-value: 1.085e-08
lm.both %>% summary
#> 
#> Call:
#> lm(formula = rin ~ Neuron + Synapses, data = as.data.frame(colData(dds)))
#> 
#> Residuals:
#>      Min       1Q   Median       3Q      Max 
#> -1.76501 -0.49485 -0.05657  0.66050  1.64005 
#> 
#> Coefficients:
#>             Estimate Std. Error t value Pr(>|t|)    
#> (Intercept)   5.9490     0.1230  48.378  < 2e-16 ***
#> Neuron       -1.7596     0.5442  -3.234  0.00226 ** 
#> Synapses      2.9213     0.5442   5.369 2.53e-06 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 0.8608 on 46 degrees of freedom
#> Multiple R-squared:  0.6954, Adjusted R-squared:  0.6822 
#> F-statistic: 52.51 on 2 and 46 DF,  p-value: 1.334e-12
lm.both.cohort %>% summary
#> 
#> Call:
#> lm(formula = rin ~ Neuron + Synapses + origin, data = as.data.frame(colData(dds)))
#> 
#> Residuals:
#>      Min       1Q   Median       3Q      Max 
#> -1.68523 -0.54675 -0.01054  0.52954  1.62786 
#> 
#> Coefficients:
#>             Estimate Std. Error t value Pr(>|t|)    
#> (Intercept)   6.0734     0.2152  28.222  < 2e-16 ***
#> Neuron       -1.5875     0.5989  -2.651    0.011 *  
#> Synapses      2.7036     0.6280   4.305  8.9e-05 ***
#> originPW     -0.2177     0.3082  -0.706    0.484    
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 0.8655 on 45 degrees of freedom
#> Multiple R-squared:  0.6988, Adjusted R-squared:  0.6787 
#> F-statistic: 34.79 on 3 and 45 DF,  p-value: 8.653e-12

Synaptosome expression seem to explain RIN the best. But what if we consider “origin” as well? Which is the “best” model? Check the adjusted R squared for the models or run anova() on two models to test whether a more complex model is significantly better at capturing RIN variation.

Add “origin” as a covariate to the previous models and compare the adjusted R squared between the simpler and more comples versions of the model. Even better, use the anova() function to compare the models.

anova(
    lm(rin~Synapses, data=as.data.frame(colData(dds))), 
    lm(rin~Synapses+Neuron, data=as.data.frame(colData(dds)))
)
anova(
    lm(rin~Synapses, data=as.data.frame(colData(dds))), 
    lm(rin~Synapses+origin, data=as.data.frame(colData(dds)))
)
anova(
    lm(rin~Neuron, data=as.data.frame(colData(dds))),
    lm(rin~Neuron+origin, data=as.data.frame(colData(dds)))
)
anova(
    lm(rin~Synapses+origin, data=as.data.frame(colData(dds))),
    lm(rin~Synapses+Neuron+origin, data=as.data.frame(colData(dds)))
)
lm.final <- lm(rin~Synapses+Neuron, data=as.data.frame(colData(dds)))

You can also check the diagnostic plots by simply using the function plot() on the linear fit. Do you see a sample that you may want to exclude before running your model again? Does it make a difference?

plot(lm.final)

4 Differential gene expression

4.1 Running an differential expression analysis

With the DESeq2 package, we can use the function DESeq() on our dds object to run the whole differential expression pipeline, which will run three steps in order:

  1. estimateSizeFactors(dds): estimation of sample-specific normalization parameters
  2. estimateDispersions(dds): estimation of gene-specific dispersion parameters
  3. nbinomWaldTest(dds): negative binomial generalized linear model to calculate the desired log2-fold changes and calculation of Wald statistics

When you run DESeq, by default it will calculate the fit according to whatever model you had specified when you created the object. The model’s formula can be printed on screen using design(dds).

Let’s change the model’s formula of the object like so:

design(dds) <- ~ sex + age_years + condition

and then run DESeq() on the dds object.

Note 1: if you have manually changed something in the colData(dds), it may be that some columns are of type “character”. They should be converted to factors.

Note 2: to avoid some headaches later on, we want to be very explicit with our variable of interest (the disease status) by specifying which of the two levels (“case” or “control”) is the REFERENCE value. This can be done by using the relevel() function like so:

colData(dds)$condition <- relevel(colData(dds)$condition, ref="Control")

Run a differential expression analysis on the dds object using the DESeq() function. The model’s formula should be ~ sex + age + condition. Overwrite with the returned object the dds object.

Useful functions:

  • mutate_if()
  • is.character()
  • DESeq()
# convert to char to factor
chr_cols <- sapply(colData(dds), is.character)
colData(dds)[chr_cols] <- lapply(colData(dds)[chr_cols], as.factor)

# relevel condition just in case
colData(dds)$condition <- relevel(colData(dds)$condition, ref="Control")

design(dds) <- ~ sex + age_years + condition
dds <- DESeq(dds)

4.2 Looking at the results

The results() function extracts the log2 fold changes and p values from the dds object (as long as the DESeq function was run). By default, the function will extract the log2 fold change for the last variable in the design formula (in our case, the disease status). However, it is always good practice to explicitly specify the contrast when calling results() with the “contrast” or “name” argument. There are different ways of specifying contrasts but, for example, if we wanted to extract the differential expression results between males and females, we could use:

results(dds, name="sex_M_vs_F")

or

results(dds, contrast=c("sex", "M", "F"))

they are equivalent. A nice trick to see which contrasts can be extracted using “name=”, you can use

resultsNames(dds)

which prints all possible values for the contrast name. In our case, the possible values for the “name=” argument are: “Intercept”, “sex_M_vs_F”, “age_years”, and “condition_Case_vs_Control”

Use the results() function on the dds object to extract the differential expression between cases and controls and find out whether any genes are significant after correction.

#results(dds, name="sex_M_vs_F")
#results(dds, contrast=c("sex", "M", "F"))
res <- results(dds, name="condition_Case_vs_Control") %>% as_tibble(rownames="gene_name")
res %>% arrange(padj)

Are there any significant changes in gene expression associated with the disease?

We have seen that RIN is associated with the main lines of variation in gene expression. We should expect, then, that RIN is adding a lot of “uninteresting” variation to the dataset, i.e., a lot of noise. What happens if you add “rin” as a covariate?

You’ll need to update the formula and re-run the whole pipeline like so:

design(dds) <- ~ sex + age_years + rin + condition
dds <- DESeq(dds)
res <- results(dds, name="condition_Case_vs_Control") %>% as_tibble(rownames="gene_name")
res %>% arrange(padj)

Re-run the analyses with RIN as a covariate.

dds_rin <- dds
design(dds_rin) <- ~ sex + age_years + rin + condition
dds_rin <- DESeq(dds_rin)
res_rin <- results(dds_rin, name="condition_Case_vs_Control") %>% as_tibble(rownames="gene_name")
res_rin %>% arrange(padj)

What do you think has changed? How does the extra covariate added influence the results? What do you think would have happened if you added to the model a covariate that was highly associated with the condition of interest (i.e., “confounded”)?

Since the cohorts had different RINs and post-mortem intervals, try to run the cohorts separately and explore the results.

Run the differential expression analyses separately for each cohort.

dds_pw <- dds[,colData(dds)$origin=="PW"]
dds_pw <- DESeq(dds_pw)
results(dds_pw) %>% as_tibble(rownames="gene_name") %>% arrange(padj)
dds_nbb <- dds[,colData(dds)$origin=="NBB"]
dds_nbb <- DESeq(dds_nbb)
results(dds_nbb) %>% as_tibble(rownames="gene_name") %>% arrange(padj)

How can two cohorts exhibit such differences in their results? What does this suggest about inter-study replicability?

5 Gene set enrichment analyse

We are going to use the fgsea package to run a gene set enrichment analysis on your results.

Install the fgsea package using Bioconductor::install(fgsea) and load the package.

require('fgsea')

We can download “gmt” formatted files from MSigDB website, which can be imported into R using the gmtPathways() function from the fgsea package. I have already downloaded the KEGG database, which can be loaded into a variable like so:

KEGG_pathways <- gmtPathways("c2.cp.kegg.v2022.1.Hs.symbols.gmt")

Load the KEGG pathway list using the previous command.

KEGG_pathways <- gmtPathways("c2.cp.kegg.v2022.1.Hs.symbols.gmt")

We are going to prepare an input for the fgsea() function. We need to rank the genes according to our value of interest (in our case, it will be the “stat” column of our differential expression results, although you could use -log10(p-value) or log2FoldChange).

Create a vector with the “stat” values from the results and name the values with the corresponding gene names.

ranks <- res$stat
names(ranks) <- res$gene_name

Run gsea_res <- fgsea(KEGG_pathways, ranks) and explore the results (“ranks” is the vector of differential expression stats).

gsea_res <- fgsea(KEGG_pathways, ranks) %>% arrange(padj)
gsea_res

What is at the top? Can you run the GSEA with the results from the two alternate models (i.e., with and withouth accounting for the effect of RIN)?

Create a new vector with the stats of the appropriate results and run fgsea again.

ranks_rin <- res_rin$stat
names(ranks_rin) <- res_rin$gene_name
gsea_res_rin <- fgsea(KEGG_pathways, ranks_rin) %>% arrange(padj)
gsea_res_rin

What happens if you add Neuron or Synapses to the design model? How that does affect the GSEA results?

Run another differential expression analysis using Neuron and/or Synapses as covariates and explore the differences in the enriched pathways.

dds_neu <- dds
design(dds_neu) <- ~ sex + age_years + rin + Neuron + Synapses + condition
dds_neu <- DESeq(dds_neu)
res_neu <- results(dds_neu) %>% as_tibble(rownames="gene_name")
ranks_neu <- res_neu$stat
names(ranks_neu) <- res_neu$gene_name
gsea_res_neu <- fgsea(KEGG_pathways, ranks_neu) %>% arrange(padj)
gsea_res_neu

Can you think of a way to identify the pathways that are more susceptible to RIN and/or Neuron correction?

LS0tDQp0aXRsZTogIlBhcnQgMjogUk5BLXNlcSBhbmFseXNpcyINCmF1dGhvcjogIkdvbnphbG8gUy4gTmlkbyINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDoNCiAgICBodG1sX25vdGVib29rOg0KICAgICAgICB0b2M6IHRydWUNCiAgICAgICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQ0KICAgICAgICBoaWdobGlnaHQ6IHRhbmdvDQotLS0NCg0KPCEtLQ0KVG8gY29udmVydCBNYXJrZG93biBkb2N1bWVudCB0byBIVE1MLA0KUiAtZSAicm1hcmtkb3duOjpyZW5kZXIoJzIuUm1kJykiDQoNClRvIHNwZWxsLWNoZWNrLCBzcGVsbGluZzo6c3BlbGxfY2hlY2tfZmlsZXMoKQ0KLS0+DQoNCmBgYHtjc3MsIGVjaG89RkFMU0UsIGNhY2hlPUZBTFNFfQ0KcSB7IGNvbG9yOiBCbGFjazsgZm9udC13ZWlnaHQ6IGJvbGQ7IGJhY2tncm91bmQtY29sb3I6IGtoYWtpOyB9DQpgYGANCg0KDQpgYGB7ciBnbG9iYWxfb3B0aW9ucywgaW5jbHVkZT1GQUxTRSwgY2FjaGU9RkFMU0V9DQpyZXF1aXJlKCJrbml0ciIpDQprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLnBhdGg9J0ZpZ3NfUm1kLycsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTMsDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBlY2hvPVRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgI2VjaG89RkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgZXZhbD1UUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICNldmFsPUZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdHM9VFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAjcmVzdWx0cz1GQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBjYWNoZT1GQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBjb21tZW50PSIjPiIpDQpyZXF1aXJlKCJrYWJsZUV4dHJhIikNCmBgYA0KDQoNCg0KDQoNCg0KDQoNCg0KIyBQYWNrYWdlcw0KDQpBcyB1c3VhbCwgd2Ugd2lsbCBuZWVkIHRvIGxvYWQgc29tZSBwYWNrYWdlcy4NCg0KDQo8cT5Mb2FkIHRoZSBgdGlkeXZlcnNlYCBhbmQgYERFU2VxMmAgcGFja2FnZXMsIGluc3RhbGwgdGhlbSBpZiB0aGV5IGFyZSBub3QNCmFscmVhZHkuPC9xPg0KDQoNCmBgYHtyfQ0KcmVxdWlyZSgidGlkeXZlcnNlIikNCnJlcXVpcmUoIkRFU2VxMiIpDQpgYGANCg0KTm93LCBsb2FkIHRoZSBERVNlcTIgb2JqZWN0IHRoYXQgeW91IHNhdmVkIHByZXZpb3VzbHkgdXNpbmcgdGhlIGByZWFkUkRTYA0KZnVuY3Rpb24uDQoNCjxxPkxvYWQgdGhlIFJkcyBvYmplY3QgY29udGFpbmluZyB0aGUgREVTZXEyIG9iamVjdCBpbnRvIHRoZSAiZGRzIiB2YXJpYWJsZTwvcT4NCg0KYGBge3J9DQpkZHMgPC0gcmVhZFJEUygiLi9kZHMuUmRzIikNCmBgYA0KDQpXaGlsZSB3ZSBhbHJlYWR5IGFwcGxpZWQgc29tZSBmaWx0ZXJpbmcgdGhlIHRvIGNvdW50IG1hdHJpeCB0aGF0IHdlIHVzZWQgYXMgYW4NCmlucHV0LCB3ZSBjYW4gc3RpbGwgcHJlLWZpbHRlciB0aGUgZGF0YXNldCBmdXJ0aGVyLiBZb3UgY2FuLCBpbiBmYWN0LCB0cmVhdCB0aGUNCkRFU2VxRGF0YVNldCBvYmplY3QgKGluIG91ciBgZGRzYCB2YXJpYWJsZSkgYXMgYSBtYXRyaXggdG8gc3Vic2V0IGl0Lg0KDQpGb3IgZXhhbXBsZSwgYGRpbShkZHMpYCB3aWxsIHJldHVybiB0aGUgZGltZW5zaW9ucyBvZiB0aGUgbWF0cml4IChudW1iZXIgb2YNCmZlYXR1cmVzIG9yIGdlbmVzLCBhbmQgbnVtYmVyIG9mIHNhbXBsZXMpLiBTaW1pbGFybHksIHdlIGNhbiBzdWJzZXQgdG8gdGhlDQpmaXJzdCAzIHNhbXBsZXMgYW5kIGZpcnN0IDEwIGdlbmVzIHdpdGggYGRkc1sxOjEwLDE6M11gLg0KDQpUbyBhY2Nlc3MgdGhlIGZ1bGwgY291bnQgbWF0cml4LCB5b3UgY2FuIHVzZSBgY291bnRzKGRkcylgLCBhbmQgdG8gYWNjZXNzIHRoZQ0Kc2FtcGxlIG1ldGFkYXRhLCB5b3UgdXNlIGBjb2xEYXRhKGRkcylgLg0KDQojIFNvbWUgbW9yZSBwcmVmaWx0ZXJpbmcNCg0KIyMgUHJlLWZpbHRlcmluZyBnZW5lcw0KDQpJdCBpcyBjdXN0b21hcnkgdG8gcmVtb3ZlIGdlbmVzIHRoYXQgaGF2ZSB2ZXJ5IGxvdyBleHByZXNzaW9uLCBzaW5jZSB0aGVpcg0Kc2lnbmFsLXRvLW5vaXNlIHJhdGlvIGlzIHZlcnkgc21hbGwgdG8geWllbGQgYW55IGJpb2xvZ2ljYWxseSBzaWduaWZpY2FudA0KcmVzdWx0cy4NCg0KPHE+S2VlcCBpbiB0aGUgYGRkc2Agb2JqZWN0IG9ubHkgZ2VuZXMgd2l0aCBhdCBsZWFzdCAxMCByZWFkcyBpbiBtb3JlIHRoYW4gMjUlDQpvZiB0aGUgc2FtcGxlczwvcT4NCg0KVXNlZnVsIGZ1bmN0aW9uczoNCg0KKiBgcm93U3VtcygpYA0KDQpgYGB7cn0NCm5yb3coZGRzKQ0KblNhbXBsZXMgPC0gbmNvbChkZHMpKi4yNQ0KZ2VuZXMua2VlcCA8LSByb3dTdW1zKGNvdW50cyhkZHMpID49MTApID4gblNhbXBsZXMNCmRkcyA8LSBkZHNbZ2VuZXMua2VlcCxdDQpucm93KGRkcykNCmBgYA0KDQpXZSB3ZW50IGZyb20gYWJvdXQgMjAsMjAwIGdlbmVzIGRvd24gdG8gYWJvdXQgMTcsNDAwLg0KDQoNCiMjIFNhbXBsZSBjaGFyYWN0ZXJpemF0aW9uDQoNCk5vdyB3ZSB3YW50IHRvIGV4cGxvcmUgdGhlIHNhbXBsZXMgaW4gb3VyIGRhdGFzZXQuIEluIG9yZGVyIHRvIGNvbXBhcmUgdGhlDQpjb2x1bW5zIG9mIG91ciBtYXRyaXgsIGhvd2V2ZXIsIHdlIG5lZWQgdG8gbm9ybWFsaXplIHRoZSBjb3VudHMuIFRoZSBzaW1wbGVzdA0Kd2F5IHdvdWxkIGJlIHRvIHRha2UgdGhlIHN1bSBvZiBlYWNoIGNvbHVtbiAoaS5lLiwgdGhlIHRvdGFsIGxpYnJhcnkgc2l6ZSBvZg0KZWFjaCBzYW1wbGUpIGFuZCBkaXZpZGUgdGhlIGNvdW50cyBieSB0aGUgdmFsdWUuIFRoZSBgREVTZXEyYCBwYWNrYWdlIG9mZmVycw0Kc29tZSBtb3JlIHNvcGhpc3RpY2F0ZWQgbWV0aG9kcywgYW5kIHdlIHdpbGwgdXNlIHRoZSAidmFyaWFuY2Ugc3RhYmlsaXNpbmcNCnRyYW5zZm9ybWF0aW9uIiBpbiB0aGlzIHR1dG9yaWFsICh0aGUgYHZzdCgpYCBmdW5jdGlvbikuICBUaGUgb3V0cHV0IGlzIGENCmBERVNlcVRyYW5zZm9ybWAgb2JqZWN0LCBhbmQgdGhlIHZhbHVlcyBjYWxjdWxhdGVkIGNhbiBiZSBleHRyYWN0ZWQgZnJvbSBpdA0KdXNpbmcgdGhlIGBhc3NheSgpYCBmdW5jdGlvbjoNCg0KICAgIG5vcm1fZXhwciA8LSB2c3QoZGRzKSAlPiUgYXNzYXkNCg0KPHE+RXhlY3V0ZSB0aGUgY29tbWFuZCBhYm92ZSB0byBvYnRhaW4gYSBtYXRyaXggb2Ygbm9ybWFsaXplZCBnZW5lDQpleHByZXNzaW9uPC9xPg0KDQpgYGB7cn0NCm5vcm1fZXhwciA8LSB2c3QoZGRzKSAlPiUgYXNzYXkNCmBgYA0KDQpCZWZvcmUgd2UgcGxvdCB0aGUgZXhwcmVzc2lvbiB2YWx1ZXMgd2UganVzdCBjYWxjdWxhdGVkLCBsZXQncyBpbnN0YWxsIGFuZCBsb2FkDQp0aGUgYHBoZWF0bWFwYCBwYWNrYWdlLCB1c2VkIHRvIHBsb3QgaGVhdG1hcHMuDQoNCmBgYHtyfQ0KcmVxdWlyZSgicGhlYXRtYXAiKQ0KYGBgDQoNCk5vdywgaW5zdGVhZCBvZiBwbG90dGluZyB0aGUgd2hvbGUgbm9ybWFsaXplZCBleHByZXNzaW9uIG1hdHJpeCB0aGF0IHdlDQpvYnRhaW5lZCB1c2luZyB0aGUgYHZzdCgpYCBmdW5jdGlvbiwgbGV0IHVzIG9ubHkgcGxvdCB0aGUgdG9wIDEsMDAwIG1vc3QNCnZhcnlpbmcgZ2VuZXMgdXNpbmcgdGhlIGBwaGVhdG1hcCgpYCBmdW5jdGlvbi4NCg0KPHE+Q2FsY3VsYXRlIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gZm9yIGVhY2ggZ2VuZSBhbmQgcGljayB0aGUgdG9wIDEsMDAwIGdlbmVzDQp3aXRoIHRoZSBoaWdoZXN0LiBQbG90IHRoZSB2c3QgZXhwcmVzc2lvbiBtYXRyaXggZm9yIHRob3NlIGdlbmVzIHVzaW5nIHRoZQ0KYHBoZWF0bWFwKClgIGZ1bmN0aW9uLjwvcT4NCg0KVXNlZnVsIGZ1bmN0aW9uczoNCg0KKiBgYXBwbHkoKWANCiogYHNkKClgDQoqIGByb3dTZHMoKWANCiogYGFycmFuZ2UoKWANCiogYGRlc2MoKWANCiogYHNsaWNlX21heCgpYA0KKiBgcGhlYXRtYXAoKWANCg0KYGBge3IsIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD0xMH0NCnBsb3QuZ2VuZXMgPC0gdGliYmxlKGdlbmVfbmFtZT1yb3duYW1lcyhub3JtX2V4cHIpLCBzZD1ub3JtX2V4cHIgJT4lIHJvd1NkcyApICU+JQ0KICAgIHNsaWNlX21heChzZCwgbj0xMDAwKSAlPiUNCiAgICBwdWxsKGdlbmVfbmFtZSkNCm5vcm1fZXhwcltwbG90LmdlbmVzLCBdICU+JSBwaGVhdG1hcCgpDQpgYGANCg0KVGhlIGhlYXRtYXAgaXMgbm90IHRoYXQgaW5mb3JtYXRpdmUgdG8gY29tcGFyZSBhY3Jvc3Mgc2FtcGxlcywgYmVjYXVzZSBpdCBvbmx5DQpzaG93cyB0aGF0IHNvbWUgZ2VuZXMgaGF2ZSBhIGhpZ2hlciBsZXZlbCBvZiBleHByZXNzaW9uIHRoYW4gb3RoZXJzLiBCeQ0KZGVmYXVsdCwgYHBoZWF0bWFwKClgIGRvZXMgbm90IHNjYWxlIHRoZSBkYXRhLCBidXQgd2UgY2FuIGRvIHRoYXQgdXNpbmcNCnNjYWxlPSJyb3ciIG9yIHNjYWxlPSJjb2x1bW4iLg0KDQo8cT5QbG90IHRoZSBoZWF0bWFwIGFnYWluIGJ1dCB0aGlzIHRpbWUgdXNpbmcgc2NhbGU9InJvdyI8L3E+DQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9MTB9DQpub3JtX2V4cHJbcGxvdC5nZW5lcywgXSAlPiUNCiAgICBwaGVhdG1hcChzY2FsZT0icm93IikNCmBgYA0KDQpXaGF0IGNvdWxkIHRoZSB0d28gY2x1c3RlcnMgYmUgcmVwcmVzZW50aW5nPyBXZSBjYW4gYW5ub3RhdGUgdGhlIGNvbHVtbnMgb2YgdGhlDQpoZWF0bWFwIGJ5IHVzaW5nIHRoZSBvcHRpb24gImFubm90YXRpb25fY29sIiBvZiBgcGhlYXRtYXAoKWAuIFdlIG5lZWQgdG8NCnByb3ZpZGUgYSBgZGF0YS5mcmFtZWAgd2l0aCB0aGUgdmFyaWFibGVzIHRvIGFubm90YXRlLiBGb3IgZXhhbXBsZToNCg0KICAgIGFubm90YXRpb25fY29sPWFzLmRhdGEuZnJhbWUoY29sRGF0YShkZHMpWyxjKCJjb25kaXRpb24iLCJvcmlnaW4iKV0pDQoNCjxxPiBQbG90IHRoZSBzYW1lIGhlYXRtYXAgYnV0IGFkZGluZyBhbm5vdGF0aW9ucyBmb3IgdGhlIGNvbmRpdGlvbiwgdGhlIG9yaWdpbiwNCnRoZSBSSU4gdmFsdWVzIGFuZCB0aGUgcG9zdC1tb3J0ZW0gdGltZTwvcT4NCg0KYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0NCm5vcm1fZXhwcltwbG90LmdlbmVzLCBdICU+JQ0KICAgIHBoZWF0bWFwKHNjYWxlPSJyb3ciLCBhbm5vdGF0aW9uX2NvbD1hcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKVssYygiY29uZGl0aW9uIiwib3JpZ2luIiwgInJpbiIsICJwbV90aW1lX21pbiIpXSkpDQpgYGANCg0KVGhlIGZhY3QgdGhhdCB0aGUgdHdvIGNvaG9ydHMgYXJlIHNvIGRpZmZlcmVudCBpcyBhIHJlYXNvbiBmb3IgY29uY2Vybi4gV2Uga25vdw0KdGhhdCAgdGhlIGNvaG9ydHMgaGF2ZSB2ZXJ5IGRpZmZlcmVudCBSSU4gdmFsdWVzIGFuZCBwb3N0LW1vcnRlbSBpbnRlcnZhbHMsIGFuZA0KZ2VuZSBleHByZXNzaW9uIG1heSByZWZsZWN0IHRob3NlIGRpZmZlcmVuY2VzLg0KDQoNCiMjIyBQYWlyd2lzZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHNhbXBsZXMNCg0KV2UgYXJlIGdvaW5nIHRvIGNhbGN1bGF0ZSBob3cgZGlmZmVyZW50ICplYWNoIHBhaXIqIG9mIHNhbXBsZXMgYXJlLCBhbmQgdGhlbg0Kc3VtbWFyaXplIGVhY2ggc2FtcGxlIGJ5IGl0cyBtZWRpYW4gZGlmZmVyZW5jZSB0byB0aGUgcmVzdCBvZiB0aGUgc2FtcGxlcy4NCg0KVGhlIGBjb3IoKWAgZnVuY3Rpb24gY2FsY3VsYXRlcyB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiBlYWNoIHBhaXIgb2YgY29sdW1ucyBvZg0KdGhlIGlucHV0IG1hdHJpeCwgYW5kIHdlIGNhbiB0YWtlIGFkdmFudGFnZSBvZiBpdC4NCg0KPHE+VXNlIHRoZSBgY29yKClgIGZ1bmN0aW9uIG9uIHRoZSBzdWJzZXQgb2YgMSwwMDAgbW9zdCB2YXJ5aW5nIGdlbmVzIGZyb20gdGhlDQp2c3QgdHJhbnNmb3JtZWQgZ2VuZSBleHByZXNzaW9uIGRhdGEgdG8gY2FsY3VsYXRlIGFsbCBwYWlyd2lzZSBjb3JyZWxhdGlvbnMgYW5kDQpwbG90IHRoZSByZXN1bHRpbmcgY29ycmVsYXRpb24gbWF0cml4IHVzaW5nIGBwaGVhdG1hcCgpYC48L3E+DQoNClVzZWZ1bCBmdW5jdGlvbnM6DQoNCiogYGNvcigpYA0KKiBgcGhlYXRtYXAoKWANCg0KYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0NCkNvcnMgPC0gY29yKG5vcm1fZXhwcltwbG90LmdlbmVzLCBdKQ0KcGhlYXRtYXAoQ29ycykNCmBgYA0KDQo8cT5DYWxjdWxhdGUgdGhlIG1lZGlhbiBjb3JyZWxhdGlvbiBwZXIgc2FtcGxlLjwvcT4NCg0KVXNlZnVsIGZ1bmN0aW9uczoNCg0KKiBgcm93TWVkaWFucygpYA0KKiBgc29ydCgpYA0KDQoNCmBgYHtyfQ0KbWVkQ29ycyA8LSByb3dNZWRpYW5zKENvcnMpDQpuYW1lcyhtZWRDb3JzKSA8LSByb3duYW1lcyhDb3JzKQ0KbWVkQ29ycyAlPiUgc29ydA0KYGBgDQoNCkEgc3RyYWlnaHRmb3J3YXJkIHdheSB0byBkaXNwbGF5IG91dGxpZXJzIGluIHRoaXMgY2FzZSBpcyB0byBwbG90IGEgYm94cGxvdCB3aXRoDQpnZ3Bsb3QyLCB3aGljaCBieSBkZWZhdWx0IHBsb3RzIG91dGxpZXJzIGFzIHBvaW50czoNCg0KICAgIGdncGxvdCh0aWJibGUobWVkQ29ycykpICsgZ2VvbV9ib3hwbG90KGFlcyh4PTEsIHk9bWVkQ29ycykpDQoNCjxxPlBsb3QgYSBnZ3Bsb3QyIGJveHBsb3QgdXNpbmcgdGhlIGNvbW1hbmQgYWJvdmUuPC9xPg0KDQpgYGB7cn0NCmdncGxvdCh0aWJibGUobWVkQ29ycykpICsgZ2VvbV9ib3hwbG90KGFlcyh4PTEsIHk9bWVkQ29ycykpDQpgYGANCg0KV2UgY2FuIHNlZSB0aGF0IGZvdXIgc2FtcGxlcyAoU0wyODM1ODUsIFNMMjgzNTk3LCBTTDI4MzU5MCwgU0wyODM1NzUpIGFyZSBzaG93bg0KYXMgb3V0bGllcnMgLSB0aGV5IGFyZSBxdWl0ZSBkaWZmZXJlbnQgZnJvbSBhbGwgdGhlIHJlc3QuDQoNCk5vdyB3ZSB3aWxsIHBsb3QgYWxsIHRoZSBzYW1wbGVzIGluIHRoZSBmaXJzdCAyIHByaW5jaXBhbCBjb21wb25lbnRzIG9mIGEgUENBLg0KVGhlIGVhc2llc3QgaXMgcHJvYmFibHkgdG8gdXNlIHRoZSBgcGxvdFBDQSgpYCBmdW5jdGlvbiBmcm9tIHRoZSBgREVTZXEyYA0KcGFja2FnZSwgd2hpY2ggd29ya3Mgb24gdGhlIHJlc3VsdCBmcm9tIGB2c3QoKWAuIE5vdGUgdGhhdCB5b3UgaGF2ZSB0byBydW4NCmB2c3QoKWAgYWdhaW4gb24gdGhlIGBkZHNgIG9iamVjdCwgc2luY2UgYHBsb3RQQ0EoKWAgd29ya3Mgb24gaXRzIG91dHB1dC4NCg0KVXNlZnVsIGZ1bmN0aW9uczoNCg0KKiBgdnN0KClgDQoqIGBwbG90UENBKClgDQoNCjxxPlVzZSB0aGUgYHBsb3RQQ0EoKWAgZnVuY3Rpb24gdG8gdmlzdWFsaXplIHRoZSBzYW1wbGVzIGluIHRoZSBmaXJzdCAyDQpwcmluY2lwYWwgY29tcG9uZW50cyBvZiB0aGUgZXhwcmVzc2lvbiBkYXRhLjwvcT4NCg0KYGBge3IsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTV9DQpwbG90UENBKHZzdChkZHMpKQ0KYGBgDQoNCjxxPkRvIHRoZSBzYW1lLCBidXQgZm9yIGVhY2ggY29ob3J0IChvcmlnaW4pIHNlcGFyYXRlbHkuPC9xPg0KDQpgYGB7ciwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9NX0NCnBsb3RQQ0EodnN0KGRkcylbLGNvbERhdGEoZGRzKSRvcmlnaW49PSJQVyJdKQ0KcGxvdFBDQSh2c3QoZGRzKVssY29sRGF0YShkZHMpJG9yaWdpbj09Ik5CQiJdKQ0KYGBgDQoNCkNhbiB5b3Ugc3BvdCB0aGUgdHdvIGNsdXN0ZXJzIGluIHRoZSBOQkIgY29ob3J0Pw0KDQpUaGUgYHBsb3RQQ0EoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYERFU2VxMmAgcGFja2FnZSBkb2VzIHNvbWV0aGluZyBzbmVha3kgYmVoaW5kDQp0aGUgc2NlbmVzLiBJZiB5b3UgY2hlY2sgdGhlIGhlbHAgZm9yIHRoZSBmdW5jdGlvbiwgeW91J2xsIHNlZSB0aGF0IHRoZQ0KYXJndW1lbnQgIm50b3AiIGlzIHNldCB0byA1MDAgYnkgZGVmYXVsdC4gVG8gc2F2ZSB0aW1lLCB0aGUgZnVuY3Rpb24gY2Fycmllcw0Kb3V0IHRoZSBQQ0Egb25seSBvbiB0aGUgdG9wIDUwMCBtb3N0IHZhcnlpbmcgZ2VuZXMsIHNpbWlsYXJseSB0byB3aGF0IHdlIHdlcmUNCmRvaW5nIGJlZm9yZSAoYnV0IHVzaW5nIHRoZSB0b3AgMSwwMDApLiBXaGF0IGhhcHBlbnMgaWYgd2UgdXNlLCBmb3IgZXhhbXBsZSwNCm9ubHkgdGhlIHRvcCAxMDAgaW5zdGVhZD8NCg0KPHE+VXNlIGBwbG90UENBKClgIHdpdGggbnRvcD00MDAsIHRoZW4gbnRvcD0yMDAsIGFuZCBmaW5hbGx5IHdpdGggbnRvcD0xMCBmb3INCmFsbCBzYW1wbGVzLjwvcT4NCg0KYGBge3IsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTV9DQpwbG90UENBKHZzdChkZHMpLCBudG9wPTQwMCkNCnBsb3RQQ0EodnN0KGRkcyksIG50b3A9MjAwKQ0KcGxvdFBDQSh2c3QoZGRzKSwgbnRvcD0xMCkNCmBgYA0KDQpUaGUgZmV3ZXIgZ2VuZXMgd2UgdXNlIHRvIGNvbnN0cnVjdCB0aGUgUENBLCB0aGUgbW9yZSBvYnZpb3VzIHRoZSBjbHVzdGVycw0KYmVjb21lLiBXaGF0IGlzIGdvaW5nIG9uPyBXaGF0IGNvdWxkIGl0IGJlIHRoYXQgc2VwYXJhdGVzIG91ciBzYW1wbGVzIGluIHR3bw0KZ3JvdXBzPyBMZXQncyBoYXZlIGEgbG9vayBhdCB0aGlzIHRvcCAxMCBtb3N0IHZhcnlpbmcgZ2VuZXMuDQoNCkZpcnN0LCBsb2FkIHRoZSBnZW5lIGRlc2NyaXB0aW9ucyB0aGF0IHdlIHVzZWQgaW4gdGhlIGZpcnN0IHR1dG9yaWFsIHVzaW5nDQoNCiAgICBnZW5lTmFtZXMgPC0gcmVhZFJEUyh1cmwoImh0dHBzOi8vZ2l0LmFwcC51aWIubm8vbmV1cm9taWNzL2NlbGwtY29tcG9zaXRpb24tcm5hLXBkLy0vcmF3L21hc3Rlci9EYXRhL0Vuc0RiLkhzYXBpZW5zLnY3NS5SZHMiKSkNCg0KPHE+TG9hZCB0aGUgZ2VuZU5hbWVzLlJkcyBmaWxlIGludG8gdGhlIFIgc2Vzc2lvbiBhbmQgY29udmVydCBpdCB0byBhDQp0aWJibGU8L3E+DQoNCmBgYHtyfQ0KZ2VuZU5hbWVzIDwtIHJlYWRSRFModXJsKCJodHRwczovL2dpdC5hcHAudWliLm5vL25ldXJvbWljcy9jZWxsLWNvbXBvc2l0aW9uLXJuYS1wZC8tL3Jhdy9tYXN0ZXIvRGF0YS9FbnNEYi5Ic2FwaWVucy52NzUuUmRzIikpICU+JQ0KICAgIGFzX3RpYmJsZQ0KYGBgDQoNClRoZW4sIGZpbmQgdGhlIGdlbmUgbmFtZXMgb2YgdGhlIHRvcDEwIG1vc3QgdmFyeWluZyBnZW5lcyBpbiB0aGUgYGdlbmVOYW1lc2ANCm9iamVjdC4NCg0KPHE+RmluZCB0aGUgdG9wMTAgbW9zdCB2YXJ5aW5nIGdlbmVzIGluIHRoZSBgZ2VuZU5hbWVzYCBvYmplY3QuPC9xPg0KICAgIA0KYGBge3J9DQp0aWJibGUoZ2VuZV9uYW1lPXJvd25hbWVzKG5vcm1fZXhwciksIHNkPW5vcm1fZXhwciAlPiUgcm93U2RzICkgJT4lDQogICAgc2xpY2VfbWF4KHNkLCBuPTEwKSAlPiUNCiAgICBsZWZ0X2pvaW4oZ2VuZU5hbWVzKSAlPiUNCiAgICBzZWxlY3QoZ2VuZV9uYW1lLCBzZXFuYW1lcykgJT4lDQogICAgdW5pcXVlDQpgYGANCg0KSW4gd2hpY2ggY2hyb21vc29tZSBhcmUgbW9zdCBvZiB0aGVzZSBnZW5lcyBsb2NhdGVkPyBDYW4gaXQgZXhwbGFpbiB0aGUgZ3JvdXBzDQp5b3Ugb2JzZXZlZCBpbiB0aGUgUENBPw0KDQo8cT5QbG90IHRoZSBQQ0EgYWdhaW4gdXNpbmcgdGhlIGBwbG90UENBKClgIGZ1bmN0aW9uIHVzaW5nICpudG9wPTEwKiBhbmQNCippbnRncm91cD0ic2V4IiouPC9xPg0KDQpgYGB7ciwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9NX0NCnBsb3RQQ0EodnN0KGRkcyksIG50b3A9MTAsIGludGdyb3VwPSJzZXgiKQ0KYGBgDQoNCkFsdGhvdWdoIHNleC1hc3NvY2lhdGVkIGdlbmUgZXhwcmVzc2lvbiBpcyBub3QgZXhjbHVzaXZlbHkgcmVzdHJpY3RlZCB0byBzZXgNCmNocm9tb3NvbWVzLCBpdCBpcyBvZiBhIG11Y2ggZ3JlYXRlciBtYWduaXR1ZGUgaW4gdGhlIHNleCBjaHJvbW9zb21lcy4NCg0KIyMgQ2hlY2sgc2V4IGFzc2lnbm1lbnQNCg0KV2UgY2FuIHVzZSB0aGUgc3Ryb25nIGFzc29jaWF0aW9uIGJldHdlZW4gc2V4LWNocm9tb3NvbWUgZ2VuZXMgYW5kIHNleCB0byBhc3Nlc3MNCnBvdGVudGlhbCBtaXMtbGFiZWxpbmcgb2Ygc2FtcGxlcy4gQSBxdWljayB3YXkgaXMgdG8gdXNlIGFsbCBnZW5lcyBsb2NhdGVkIGluDQp0aGUgWSBjaHJvbW9zb21lIHRvIHJ1biBhIFBDQS4NCg0KPHE+UGxvdCBhbGwgdGhlIHNhbXBsZXMgaW4gdGhlIHR3byBwcmluY2lwYWwgY29tcG9uZW50cyBvZiB0aGUgUENBIG9mIHRoZQ0KWS1sb2NhdGVkIGdlbmVzLCBjb2xvdXJpbmcgdGhlIHBvaW50cyBieSB0aGUgc2V4IG9mIHRoZSBzYW1wbGUuPC9xPiBOT1RFOiB1c2UNCnRoZSBgdnN0KClgIGZ1bmN0aW9uIG9uIHRoZSB3aG9sZSBkYXRhc2V0LCBzdWJzZXQgYWZ0ZXIuDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD01fQ0KWWdlbmVzIDwtIGdlbmVOYW1lcyAlPiUgZmlsdGVyKHNlcW5hbWVzID09ICJZIiwgZ2VuZV9iaW90eXBlID09ICJwcm90ZWluX2NvZGluZyIpICU+JQ0KICAgIHB1bGwoZ2VuZV9uYW1lKSAlPiUgdW5pcXVlDQp2c3QoZGRzKVtyb3duYW1lcyhkZHMpICVpbiUgWWdlbmVzLF0gJT4lIHBsb3RQQ0EobnRvcD1JbmYsIGludGdyb3VwPSJzZXgiKQ0KYGBgDQoNCkFzIHlvdSBjYW4gc2VlLCBtb3N0IG9mIHRoZSB2YXJpYW5jZSBpcyBleHBsYWluZWQgYnkgdGhlIGZpcnN0IHByaW5jaXBhbA0KY29tcG9uZW50LCBhcyBleHBlY3RlZC4gSXQgd291bGQgaW4gZmFjdCBiZSBlbm91Z2ggdG8gcGxvdCB0aGUgdmFsdWVzIGZvciB0aGUNClBDMS4NCg0KYGBgDQpjb3JyZWN0X3NpZ24gPC0gZnVuY3Rpb24oeCkgew0KICAgIGlmIChtZWRpYW4oeCk8MCkgcmV0dXJuKC0xKngpDQogICAgZWxzZSByZXR1cm4oeCkNCn0NCnBjYTEgPC0gdnN0KGRkcylbcm93bmFtZXMoZGRzKSAlaW4lIFlnZW5lcyxdICU+JSANCiAgICBhc3NheSAlPiUNCiAgICB0ICU+JQ0KICAgIHByY29tcA0KcGNhMSR4ICU+JQ0KICAgIGFzX3RpYmJsZShyb3duYW1lcz0ic2FtcGxlX2lkIikgJT4lDQogICAgc2VsZWN0KHNhbXBsZV9pZCwgUEMxKSAlPiUNCiAgICBtdXRhdGUoUEMxPWNvcnJlY3Rfc2lnbihQQzEpKSAlPiUNCiAgICBsZWZ0X2pvaW4oYXNfdGliYmxlKGNvbERhdGEoZGRzKSkpICU+JQ0KICAgIGdncGxvdChhZXMoeD1zZXgsIHk9UEMxKSkgKw0KICAgIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlPU5BKSArDQogICAgZ2VvbV9qaXR0ZXIoYWVzKGNvbG91cj1zZXgpLCBoZWlnaHQ9MCwgd2lkdGg9LjIpDQpgYGANCg0KYGBge3J9DQpjb3JyZWN0X3NpZ24gPC0gZnVuY3Rpb24oeCkgew0KICAgIGlmIChtZWRpYW4oeCk8MCkgcmV0dXJuKC0xKngpDQogICAgZWxzZSByZXR1cm4oeCkNCn0NCnBjYTEgPC0gdnN0KGRkcylbcm93bmFtZXMoZGRzKSAlaW4lIFlnZW5lcyxdICU+JSANCiAgICBhc3NheSAlPiUNCiAgICB0ICU+JQ0KICAgIHByY29tcA0KcGNhMSR4ICU+JQ0KICAgIGFzX3RpYmJsZShyb3duYW1lcz0ic2FtcGxlX2lkIikgJT4lDQogICAgc2VsZWN0KHNhbXBsZV9pZCwgUEMxKSAlPiUNCiAgICBtdXRhdGUoUEMxPWNvcnJlY3Rfc2lnbihQQzEpKSAlPiUNCiAgICBsZWZ0X2pvaW4oYXNfdGliYmxlKGNvbERhdGEoZGRzKSkpICU+JQ0KICAgIGdncGxvdChhZXMoeD1zZXgsIHk9UEMxKSkgKw0KICAgIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlPU5BKSArDQogICAgZ2VvbV9qaXR0ZXIoYWVzKGNvbG91cj1zZXgpLCBoZWlnaHQ9MCwgd2lkdGg9LjIpDQpgYGANCg0KRm9yIHNpbXBsaWNpdHksIHdlIGFyZSBnb2luZyB0byByZW1vdmUgZnJvbSB0aGUgYGRkc2Agb2JqZWN0IGFsbCBnZW5lcyBtYXBwZWQNCnRvIHRoZSBzZXggY2hyb21vc29tZXMuDQoNCjxxPlJlbW92ZSBhbGwgWCBhbmQgWSBnZW5lcyBmcm9tIHRoZSBgZGRzYCBvYmplY3QuPC9xPg0KDQpgYGB7cn0NCnNleC5nZW5lcyA8LSBnZW5lTmFtZXMgJT4lIGZpbHRlcihzZXFuYW1lcyAlaW4lIGMoIlgiLCJZIikpICU+JQ0KICAgIHB1bGwoZ2VuZV9uYW1lKSAlPiUgdW5pcXVlDQpkaW0oZGRzKQ0KZGRzIDwtIGRkc1shcm93bmFtZXMoZGRzKSAlaW4lIHNleC5nZW5lcyxdDQpkaW0oZGRzKQ0KYGBgDQoNCjxxPlJlY2FsY3VsYXRlIHRoZSBwYWlyd2lzZSBjb3JyZWxhdGlvbnMgYmV0d2VlbiBzYW1wbGVzIGFzIGJlZm9yZSB3aXRoIHRoZQ0KZmlsdGVyZWQgZGF0YXNldC4gUGxvdCB0aGUgdmFsdWVzIGFuZCBwbG90IHRoZSBQQ0Egd2l0aCBudG9wPTEwMDA8L3E+DQoNCmBgYHtyfQ0Kbm9ybV9leHByIDwtIHZzdChkZHMpICU+JSBhc3NheQ0KdG9wMTAwMC5nZW5lcyA8LSB0aWJibGUoZ2VuZV9uYW1lPXJvd25hbWVzKG5vcm1fZXhwciksIHNkPW5vcm1fZXhwciAlPiUgcm93U2RzICkgJT4lDQogICAgc2xpY2VfbWF4KHNkLCBuPTEwMDApICU+JQ0KICAgIHB1bGwoZ2VuZV9uYW1lKQ0KQ29ycyA8LSBjb3Iobm9ybV9leHByW3RvcDEwMDAuZ2VuZXMsIF0pDQptZWRDb3JzIDwtIHJvd01lZGlhbnMoQ29ycykNCm5hbWVzKG1lZENvcnMpIDwtIHJvd25hbWVzKENvcnMpDQptZWRDb3JzICU+JSBzb3J0DQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QodGliYmxlKG1lZENvcnMpKSArIGdlb21fYm94cGxvdChhZXMoeD0xLCB5PW1lZENvcnMpKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD01fQ0KcGxvdFBDQSh2c3QoZGRzKSkNCmBgYA0KDQpJbiBvcmRlciB0byBwbG90IHRoZSBzYW1wbGUgaWRzLCB3ZSBuZWVkIHRvIHVzZSB0aGUgInJldHVybkRhdGE9VFJVRSIgb3B0aW9uLA0KaW4gdGhlIGBwbG90UENBKClgIGZ1bmN0aW9uIHdoaWNoLCBpbnN0ZWFkIG9mIHBsb3R0aW5nLCB3aWxsIHRoZW4gcmV0dXJucyB0aGUNCmRhdGEgaW4gYSBmb3JtYXQgdGhhdCBjYW4gYmUgZWFzaWx5IHVzZWQgd2l0aCBnZ3Bsb3QyLCBhZGRpbmcgYSBsYXllciB3aXRoDQp0aGUgbGFiZWxzOg0KDQogICAgZ2dwbG90KGRhdGEpICsNCiAgICBnZW9tX3RleHQoYWVzKGxhYmVsPW5hbWUpLCBudWRnZV95PTEuNSwgc2l6ZT0zKQ0KDQpXZSBjb3VsZCBhbHNvLCBvZiBjb3Vyc2UsIHJ1biB0aGUgUENBIG91cnNlbHZlcyB1c2luZyBgcHJjb21wKClgLg0KDQpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9OH0NCnBsb3RQQ0EodnN0KGRkcyksIHJldHVybkRhdGE9VFJVRSkgJT4lDQogIGdncGxvdChhZXMoeD1QQzEsIHk9UEMyKSkgKyANCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyPWNvbmRpdGlvbikpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uYW1lKSwgbnVkZ2VfeT0xLjUsIHNpemU9MykNCmBgYA0KDQpFdmFsdWF0aW5nIHRoZSBQQ0EgcGxvdCBhbmQgdGhlIGhlYXRtYXAgb2YgY29ycmVsYXRpb25zLCBkbyB5b3UgdGhpbmsgaXQgaXMNCmp1c3RpZmllZCB0byByZW1vdmUgYW55IG9mIHRoZSBzYW1wbGVzPw0KDQoNCg0KDQoNCg0KIyBDZWxsIHR5cGUgZXN0aW1hdGlvbg0KDQpCZWZvcmUgcHJvY2VlZGluZyB3aXRoIHRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcywgc2luY2Ugd2UgYXJlDQpkZWFsaW5nIHdpdGggaHVtYW4gYnJhaW4gdGlzc3VlLCB3ZSBuZWVkIHRvIGludmVzdGlnYXRlIHdoYXQgdGhlIGNlbGwNCmNvbXBvc2l0aW9uIGlzIGluIG91ciBzYW1wbGVzLiBUaGlzIGlzIG9mIGZ1bmRhbWVudGFsIGltcG9ydGFuY2UgaW4gdGhlc2UgdHlwZQ0Kb2YgZGF0YXNldHMgYmVjYXVzZSAqKm1vc3Qgb2YgdGhlIHZhcmlhYmlsaXR5IGluIGdlbmUgZXhwcmVzc2lvbiBpbiBleHBsYWluZWQNCmJ5IHRoZSBjZWxsIHR5cGVzKiouDQoNCldlIHdpbGwgdXNlIGEgdmVyeSBzaW1wbGUgYXBwcm9hY2ggdG8gZXN0aW1hdGUgY2VsbCB0eXBlcyBzaW1pbGFyLCBpbiBhIHdheSwgdG8NCndoYXQgd2UgdXNlZCB0byAiZXN0aW1hdGUiIHNleDogc3VtbWFyaXplIGdlbmUgZXhwcmVzc2lvbiBvZiBhIHNldCBvZiBzZWxlY3RlZA0KbWFya2VycyBhcyB0aGVpciBmaXJzdCBwcmluY2lwYWwgY29tcG9uZW50IChQQzEpLiBUaGUgbWFya2VyIGdlbmVzIHdpbGwgYmUNCmtub3duIGNlbGwgdHlwZS1zcGVjaWZpYyBnZW5lcy4NCg0KTGV0J3MgbG9hZCBhIGxpc3Qgb2YgbWFya2VycyBvZiBjb3J0aWNhbCBjZWxsIHR5cGVzIGZyb20NCltOZXVyb2VzcHJlc3NvXShodHRwczovL3d3dy5lbmV1cm8ub3JnL2NvbnRlbnQvNC82L0VORVVSTy4wMjEyLTE3LjIwMTcpOg0KDQogICAgY29ydGljYWwubWFya2VycyA8LSByZWFkX3RzdigiLi9EYXRhL2NvcnRpY2FsX21hcmtlcnMudHN2IikNCg0KPHE+UmVhZCB0aGUgdGFiLXNlcGFyYXRlZCBmaWxlICJjb3J0aWNhbF9tYXJrZXJzLnRzdiIgaW50byBhIHRpYmJsZSA8L3E+DQoNCmBgYHtyfQ0KY29ydGljYWwubWFya2VycyA8LSByZWFkX3RzdigiLi9EYXRhL2NvcnRpY2FsX21hcmtlcnMudHN2IikNCmBgYA0KDQpOb3cgbGV0IHVzIHJ1biBhIFBDQSAoYXMgd2UgaGF2ZSBkb25lIHVudGlsIG5vdykgYnV0IHN1YnNldHRpbmcgdGhlIGdlbmVzIHRvDQp0aGUgbmV1cm9uYWwgbWFya2Vycy4gV2Ugd2lsbCB1c2UgdGhlIGBwcmNvbXAoKWAgZnVuY3Rpb24gYW5kIGV4dHJhY3QgdGhlDQp2YWx1ZXMgb2YgdGhlIFBDMSBmb3IgZWFjaCBzYW1wbGUuDQoNCjxxPlJ1biBhIFBDQSB1c2luZyBgcHJjb21wKClgIG9ubHkgb24gbmV1cm9uYWwgbWFya2Vycy4gRXhwbG9yZSB0aGUgcmVzdWx0IGFuZA0KZmluZCB3aGVyZSB0aGUgY29vcmRpbmF0ZXMgZm9yIHRoZSBmaXJzdCBQQyBhcmUuPC9xPg0KDQpgYGB7cn0NCmVzdGltYXRlX2N0IDwtIGZ1bmN0aW9uKG9iamVjdCwgZ2VuZXMsIGN0PSJQQzEiLCByZXNjYWxlPVRSVUUpIHsNCiAgICBvYmplY3QudnN0IDwtIHZzdChvYmplY3QpDQogICAgcGNhIDwtIG9iamVjdC52c3Rbcm93bmFtZXMob2JqZWN0LnZzdCkgJWluJSBnZW5lcyxdICU+JQ0KICAgICAgICBhc3NheSgpICU+JQ0KICAgICAgICB0KCkgJT4lDQogICAgICAgIHByY29tcCgpDQogICAgcmVzdWx0IDwtIGFzX3RpYmJsZShwY2EkeCwgcm93bmFtZXM9InNhbXBsZV9pZCIpICU+JQ0KICAgICAgICBzZWxlY3Qoc2FtcGxlX2lkLCBQQzEpICU+JQ0KICAgICAgICBtdXRhdGUoUEMxPWNvcnJlY3Rfc2lnbihQQzEpKQ0KICAgIGNvbG5hbWVzKHJlc3VsdCkgPC0gYygic2FtcGxlX2lkIiwgY3QpDQogICAgaWYgKHJlc2NhbGUpIHJlc3VsdFssY3RdIDwtIHNjYWxlKHJlc3VsdFssY3RdKVssMV0NCiAgICByZXR1cm4ocmVzdWx0KQ0KfQ0KbmV1cm9uYWwuZXN0IDwtIGVzdGltYXRlX2N0KGRkcywgY29ydGljYWwubWFya2VycyAlPiUgZmlsdGVyKGNlbGxfdHlwZT09Ik5ldXJvbiIpICU+JSBwdWxsKGdlbmVfbmFtZSksIGN0PSJOZXVyb24iKQ0KYGBgDQoNCk5vdyB3ZSBjYW4gZG8gdGhhdCBmb3IgZXZlcnkgb3RoZXIgY29ydGljYWwgY2VsbCB0eXBlLg0KDQo8cT5DYWxjdWxhdGUgdGhlIGZpcnN0IHByaW5jaXBhbCBjb21wb25lbnQgZm9yIHRoZSBvdGhlciBjZWxsIHR5cGVzIGFuZCBhZGQNCnRoZW0gYWxsIHRvIHRoZSBtZXRhZGF0YXRhIG9mIHRoZSBkZHMgb2JqZWN0IChgY29sRGF0YShkZHMpYCkuPC9xPg0KDQpgYGB7cn0NCmN0LmVzdCA8LSBsYXBwbHkodW5pcXVlKGNvcnRpY2FsLm1hcmtlcnMkY2VsbF90eXBlKSwgZnVuY3Rpb24oY3Qpew0KICAgIG1lc3NhZ2UocGFzdGUwKGN0LCAiLi4uIikpDQogICAgR2VuZXMgPC0gZmlsdGVyKGNvcnRpY2FsLm1hcmtlcnMsIGNlbGxfdHlwZT09Y3QpICU+JSBwdWxsKGdlbmVfbmFtZSkNCiAgICBlc3RpbWF0ZV9jdChkZHMsIGdlbmVzPUdlbmVzLCBjdD1jdCkNCn0pICU+JSBSZWR1Y2UoImxlZnRfam9pbiIsIC4pDQoNCm5ld0NvbERhdGEgPC0gbGVmdF9qb2luKGFzX3RpYmJsZShjb2xEYXRhKGRkcykpLCBjdC5lc3QsIGJ5PSJzYW1wbGVfaWQiKSAlPiUNCiAgICBhcygiRGF0YUZyYW1lIikNCnJvd25hbWVzKG5ld0NvbERhdGEpIDwtIG5ld0NvbERhdGEkc2FtcGxlX2lkDQphbGwocm93bmFtZXMoY29sRGF0YShkZHMpKSA9PSByb3duYW1lcyhuZXdDb2xEYXRhKSkNCmNvbERhdGEoZGRzKSA8LSBuZXdDb2xEYXRhDQpgYGANCg0KQW5kIGZpbmFsbHksIGRvIHRoZSBzYW1lIHdpdGggdGhlIFJOQSBtYXJrZXJzIGZvciB0aGUgY29ydGljYWwgc3luYXBzb21lLCB3aGljaA0KYXJlIG5vdCBxdWl0ZSB0aGUgc2FtZSBhcyB0aGUgc2V0IG9mIG5ldXJvbmFsIG1hcmtlcnMuIFRoZXNlIGFyZSBSTkFzIGZvdW5kIGluDQp0aGUgc3luYXBzZXMsIG9idGFpbmVkIGZyb20gW0hhZm5lciBldCBhbC4NCjIwMTldKGh0dHBzOi8vcHVibWVkLm5jYmkubmxtLm5paC5nb3YvMzEwOTc2MzkvKS4NCg0KICAgIHN5bmFwdG9zb21lLm1hcmtlcnMgPC0gcmVhZF90c3YoIi4vRGF0YS9zeW5hcHRvc29tZV9tYXJrZXJzLnRzdiIpDQogICAgDQpgYGB7cn0NCiNzeW4gPC0gcXVzYWdlOjpyZWFkLmdtdCgiLi9EYXRhL3N5bmFwdG9zb21lX3N5bWJvbHMuZ210IikgJT4lIHFkYXBUb29sczo6bGlzdDJkZigpICU+JSBhc190aWJibGUNCiNjb2xuYW1lcyhzeW4pIDwtIGMoImdlbmVfaWQiLCAiY2VsbF90eXBlIikNCiN3cml0ZV90c3Yoc3luLCAiLi9EYXRhL3N5bmFwdG9zb21lX21hcmtlcnMudHN2IikNCnN5bmFwdG9zb21lLm1hcmtlcnMgPC0gcmVhZF90c3YoIi4vRGF0YS9zeW5hcHRvc29tZV9tYXJrZXJzLnRzdiIpDQoNCnN5bi5lc3QgPC0gZXN0aW1hdGVfY3QoZGRzLCBnZW5lcz11bmlxdWUoc3luYXB0b3NvbWUubWFya2VycyRnZW5lX2lkKSwgY3Q9InN5bmFwdG9zb21lIikNCmFsbChzeW4uZXN0JHNhbXBsZV9pZCA9PSBjb2xEYXRhKGRkcykkc2FtcGxlX2lkKQ0KY29sRGF0YShkZHMpJFN5bmFwc2VzIDwtIHN5bi5lc3Qkc3luYXB0b3NvbWUNCmNvbERhdGEoZGRzKQ0KYGBgDQoNCkhvdyBjYW4gd2UgYXNzZXNzIHdoaWNoIHZhcmlhYmxlcyBhcmUgcmVzcG9uc2libGUgZm9yIG1vc3Qgb2YgdGhlIHZhcmlhdGlvbiBpbg0KZ2VuZSBleHByZXNzaW9uPyBQQ0EgaXMgdmVyeSBoZWxwZnVsIGluIHRoaXMgcmVnYXJkcy4gQnkgZGVmaW5pdGlvbiwgdGhlIGZpcnN0DQpwcmluY2lwYWwgY29tcG9uZW50IHdpbGwgbWF4aW1pemUgdGhlIHZhcmlhbmNlIHRoYXQgaXQgZXhwbGFpbnMgYnkgY29tYmluaW5nDQpsaW5lYXJseSB0aGUgdmFyaWFibGVzLiBJbiBhIHdheSwgaXQgaXMgInN1bW1hcml6aW5nIiBnZW5lIGV4cHJlc3Npb24gYnkNCm9wdGltYWxseSBwcm9qZWN0aW5nIGl0IGludG8gb25lIGRpbWVuc2lvbi4gVGhlbiwgd2l0aCB0aGUgbGVmdG92ZXIgdmFyaWFuY2UNCm5vdCBleHBsYWluZWQgYnkgUEMxLCBpdCB3aWxsIGRvIHRoZSBzYW1lLCBidXQgd2l0aCB0aGUgY29uc3RhaW50IHRoYXQgUEMyDQpuZWVkcyB0byBiZSBwZXJwZW5kaWN1bGFyIHRvIFBDMS4NCg0KVGhlIGZpcnN0IHByaW5jaXBhbCBjb21wb25lbnRzIGFyZSBvZnRlbiByZWZlcnJlZCB0byBhcyB0aGUgbWFpbiBsaW5lcyBvZg0KdmFyaWF0aW9uLCBiZWNhdXNlIHRoZXkgZ2F0aGVyIGFzIG11Y2ggYXMgcG9zc2libGUsIGFuZCBvZnRlbiBzZXJ2ZSBhcyBhIGdvb2QNCiJzdW1tYXJ5IiBvZiB0aGUgbXVsdGlkaW1lbnNpb25hbCBkYXRhLg0KDQpJZiB3ZSB3YW50IHRvIGFzc2VzcyB3aGV0aGVyIGNlbGwgdHlwZSBjb21wb3NpdGlvbiAob3Igb3RoZXIgdmFyaWFibGVzKSBleHBsYWluDQptb3N0IG9mIHRoZSB2YXJpYXRpb24sIGEgc2ltcGxlIHdheSBjb3VsZCBiZSB0byB0ZXN0IGZvciBhc3NvY2lhdGlvbiBiZXR3ZWVuDQp2YXJpYWJsZXMgYW5kIFBDcy4gVGhpcyBpcyBhIGJpdCBpbnZvbHZlZCBwcm9ncmFtbWF0aWNhbGx5LCBzbyBoZXJlIGlzIHRoZQ0KY29kZToNCg0KYGBgDQpzY2FsZS52ZWMgPC0gZnVuY3Rpb24oeCkgc2NhbGUoeClbLDFdDQoNCnBjYTIgPC0gZGRzICU+JSANCiAgICB2c3QgJT4lDQogICAgYXNzYXkgJT4lDQogICAgdCAlPiUNCiAgICBwcmNvbXANCg0KTWV0YWRhdGEucGNhIDwtIHBjYTIkeCAlPiUNCiAgICBhc190aWJibGUocm93bmFtZXM9InNhbXBsZV9pZCIpICU+JQ0KICAgIHNlbGVjdChzYW1wbGVfaWQsIFBDMTpQQzUpICU+JQ0KICAgIG11dGF0ZV9pZihpcy5udW1lcmljLCBzY2FsZS52ZWMpICU+JQ0KICAgIGxlZnRfam9pbihhc190aWJibGUoY29sRGF0YShkZHMpKSkNCg0KTWV0YWRhdGEucGNhIDwtIE1ldGFkYXRhLnBjYSAlPiUgc2VsZWN0KC1zYW1wbGVfaWQsIC1vcmlnaW4sIC1zYW1wbGVfaWRfcGFwZXIpICU+JSBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLCBhcy5mYWN0b3IpICU+JQ0KICAgIG11dGF0ZV9pZihpcy5mYWN0b3IsIGFzLmludGVnZXIpDQoNCmFsbENvcnMgPC0gbGFwcGx5KHBhc3RlMCgiUEMiLCAxOjUpLCBmdW5jdGlvbihwYykgew0KICAgIGxhcHBseShjb2xuYW1lcyhNZXRhZGF0YS5wY2EpWzY6bmNvbChNZXRhZGF0YS5wY2EpXSwgZnVuY3Rpb24odmFyKXsNCiAgICAgICAgI21lc3NhZ2UocGFzdGUwKHBjLCAiIHZzICIsIHZhcikpDQogICAgICAgIGxtKGZvcm11bGE9YXMuZm9ybXVsYShwYXN0ZTAocGMsICJ+IiwgdmFyKSksIGRhdGE9TWV0YWRhdGEucGNhKSAlPiUNCiAgICAgICAgICAgIGJyb29tOjp0aWR5KCkgJT4lIGZpbHRlcih0ZXJtIT0iKEludGVyY2VwdCkiKSAlPiUNCiAgICAgICAgICAgIG11dGF0ZShQQz1wYywgVkFSPXZhcikNCiAgICB9KSAlPiUgUmVkdWNlKCJiaW5kX3Jvd3MiLC4pDQp9KSAlPiUgUmVkdWNlKCJiaW5kX3Jvd3MiLC4pDQoNClB2YWxzIDwtIGFsbENvcnMgJT4lIHNlbGVjdChwLnZhbHVlLCBQQywgVkFSKSAlPiUNCiAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tPVZBUiwgdmFsdWVzX2Zyb209cC52YWx1ZSkNCnAubWF0IDwtIGFzLm1hdHJpeChQdmFsc1ssLTFdKQ0Kcm93bmFtZXMocC5tYXQpIDwtIFB2YWxzJFBDDQoNCkVzdGltYXRlcyA8LSBhbGxDb3JzICU+JSBzZWxlY3QoZXN0aW1hdGUsIFBDLCBWQVIpICU+JQ0KICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb209VkFSLCB2YWx1ZXNfZnJvbT1lc3RpbWF0ZSkNCmUubWF0IDwtIGFzLm1hdHJpeChFc3RpbWF0ZXNbLC0xXSkNCnJvd25hbWVzKGUubWF0KSA8LSBFc3RpbWF0ZXMkUEMNCg0KY29ycnBsb3Q6OmNvcnJwbG90KGUubWF0LCBwLm1hdD1wLm1hdCkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NH0NCnNjYWxlLnZlYyA8LSBmdW5jdGlvbih4KSBzY2FsZSh4KVssMV0NCg0KcGNhMiA8LSBkZHMgJT4lIA0KICAgIHZzdCAlPiUNCiAgICBhc3NheSAlPiUNCiAgICB0ICU+JQ0KICAgIHByY29tcA0KDQpNZXRhZGF0YS5wY2EgPC0gcGNhMiR4ICU+JQ0KICAgIGFzX3RpYmJsZShyb3duYW1lcz0ic2FtcGxlX2lkIikgJT4lDQogICAgc2VsZWN0KHNhbXBsZV9pZCwgUEMxOlBDNSkgJT4lDQogICAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIHNjYWxlLnZlYykgJT4lDQogICAgbGVmdF9qb2luKGFzX3RpYmJsZShjb2xEYXRhKGRkcykpKQ0KDQpNZXRhZGF0YS5wY2EgPC0gTWV0YWRhdGEucGNhICU+JSBzZWxlY3QoLXNhbXBsZV9pZCwgLW9yaWdpbiwgLXNhbXBsZV9pZF9wYXBlcikgJT4lIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGFzLmZhY3RvcikgJT4lDQogICAgbXV0YXRlX2lmKGlzLmZhY3RvciwgYXMuaW50ZWdlcikNCg0KYWxsQ29ycyA8LSBsYXBwbHkocGFzdGUwKCJQQyIsIDE6NSksIGZ1bmN0aW9uKHBjKSB7DQogICAgbGFwcGx5KGNvbG5hbWVzKE1ldGFkYXRhLnBjYSlbNjpuY29sKE1ldGFkYXRhLnBjYSldLCBmdW5jdGlvbih2YXIpew0KICAgICAgICAjbWVzc2FnZShwYXN0ZTAocGMsICIgdnMgIiwgdmFyKSkNCiAgICAgICAgbG0oZm9ybXVsYT1hcy5mb3JtdWxhKHBhc3RlMChwYywgIn4iLCB2YXIpKSwgZGF0YT1NZXRhZGF0YS5wY2EpICU+JQ0KICAgICAgICAgICAgYnJvb206OnRpZHkoKSAlPiUgZmlsdGVyKHRlcm0hPSIoSW50ZXJjZXB0KSIpICU+JQ0KICAgICAgICAgICAgbXV0YXRlKFBDPXBjLCBWQVI9dmFyKQ0KICAgIH0pICU+JSBSZWR1Y2UoImJpbmRfcm93cyIsLikNCn0pICU+JSBSZWR1Y2UoImJpbmRfcm93cyIsLikNCg0KUHZhbHMgPC0gYWxsQ29ycyAlPiUgc2VsZWN0KHAudmFsdWUsIFBDLCBWQVIpICU+JQ0KICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb209VkFSLCB2YWx1ZXNfZnJvbT1wLnZhbHVlKQ0KcC5tYXQgPC0gYXMubWF0cml4KFB2YWxzWywtMV0pDQpyb3duYW1lcyhwLm1hdCkgPC0gUHZhbHMkUEMNCg0KRXN0aW1hdGVzIDwtIGFsbENvcnMgJT4lIHNlbGVjdChlc3RpbWF0ZSwgUEMsIFZBUikgJT4lDQogICAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT1WQVIsIHZhbHVlc19mcm9tPWVzdGltYXRlKQ0KZS5tYXQgPC0gYXMubWF0cml4KEVzdGltYXRlc1ssLTFdKQ0Kcm93bmFtZXMoZS5tYXQpIDwtIEVzdGltYXRlcyRQQw0KDQpjb3JycGxvdDo6Y29ycnBsb3QoZS5tYXQsIHAubWF0PXAubWF0KQ0KYGBgDQoNCkRvIHlvdSBzZWUgYW55dGhpbmcgaW50ZXJlc3Rpbmc/IE1heSBpdCBiZSB0aGF0IHRoZSBSTkEgcXVhbGl0eSAoUklOKSBpcw0KYXNzb2NpYXRlZCB3aXRoIGNlbGwgdHlwZSBjb21wb3NpdGlvbj8gVGhhdCBjYW4gZWFzaWx5IGJlIHRlc3RlZCB3aXRoIGEgcXVpY2sNCmxpbmVhciByZWdyZXNzaW9uOiB0cnkgcHJlZGljdGluZyB0aGUgUklOIHZhbHVlcyB3aXRoIHRoZSBlc3RpbWF0ZWQgdmFsdWVzIGZvcg0Kc3luYXB0b3NvbWFsIGNvbnRlbnQsIHRoZW4gdHJ5IHRoZSBzYW1lIHdpdGggdGhlIGVzdGltYXRlZCBuZXVyb25hbCBjb250ZW50LA0KYW5kIHRoZW4gd2l0aCBib3RoIGF0IHRoZSBzYW1lIHRpbWUgaW4gdGhlIG1vZGVsLiBUaGUgZm9ybXVsYXMgYXJlIGFzIGZvbGxvdzoNCg0KKiByaW4gfiBTeW5hcHNlcw0KKiByaW4gfiBOZXVyb24NCiogcmluIH4gU3luYXBzZXMgKyBOZXVyb24NCg0KVGhlbiBjb21wYXJlIHRoZSByZXN1bHRpbmcgbW9kZWxzIHVzaW5nIHRoZSBgc3VtbWFyeSgpYCBmdW5jdGlvbiBvbiB0aGUgbGluZWFyDQpmaXRzLg0KDQo8cT5SdW4gMyBsaW5lYXIgcmVncmVzc2lvbnMgd2l0aCByaW4gYXMgYSByZXNwb25zZSB2YXJpYWJsZSBhbmQgMS4gU3luYXBzZXM7IDIuDQpOZXVyb247IGFuZCAzLiBTeW5hcHNlcyArIE5ldXJvbiBhcyBwcmVkaWN0b3JzLiBDaGVjayB0aGUgYWRqdXN0ZWQgUi1zcXVhcmVkDQp2YWx1ZXMgdG8gY29tcGFyZSB0aGUgbW9kZWxzLjwvcT4NCg0KYGBge3J9DQojIFJJTg0KbG0uc3luIDwtIGxtKHJpbn5TeW5hcHNlcywgZGF0YT1hcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKSkpDQpsbS5uZXUgPC0gbG0ocmlufk5ldXJvbiwgZGF0YT1hcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKSkpDQpsbS5ib3RoIDwtIGxtKHJpbn5OZXVyb24rU3luYXBzZXMsIGRhdGE9YXMuZGF0YS5mcmFtZShjb2xEYXRhKGRkcykpKQ0KbG0uYm90aC5jb2hvcnQgPC0gbG0ocmlufk5ldXJvbitTeW5hcHNlcytvcmlnaW4sIGRhdGE9YXMuZGF0YS5mcmFtZShjb2xEYXRhKGRkcykpKQ0KbG0uc3luICU+JSBzdW1tYXJ5DQpsbS5uZXUgJT4lIHN1bW1hcnkNCmxtLmJvdGggJT4lIHN1bW1hcnkNCmxtLmJvdGguY29ob3J0ICU+JSBzdW1tYXJ5DQpgYGANCg0KU3luYXB0b3NvbWUgZXhwcmVzc2lvbiBzZWVtIHRvIGV4cGxhaW4gUklOIHRoZSBiZXN0LiBCdXQgd2hhdCBpZiB3ZSBjb25zaWRlcg0KIm9yaWdpbiIgYXMgd2VsbD8gV2hpY2ggaXMgdGhlICJiZXN0IiBtb2RlbD8gQ2hlY2sgdGhlIGFkanVzdGVkIFIgc3F1YXJlZCBmb3INCnRoZSBtb2RlbHMgb3IgcnVuIGBhbm92YSgpYCBvbiB0d28gbW9kZWxzIHRvIHRlc3Qgd2hldGhlciBhIG1vcmUgY29tcGxleCBtb2RlbA0KaXMgc2lnbmlmaWNhbnRseSBiZXR0ZXIgYXQgY2FwdHVyaW5nIFJJTiB2YXJpYXRpb24uDQoNCjxxPkFkZCAib3JpZ2luIiBhcyBhIGNvdmFyaWF0ZSB0byB0aGUgcHJldmlvdXMgbW9kZWxzIGFuZCBjb21wYXJlIHRoZSBhZGp1c3RlZA0KUiBzcXVhcmVkIGJldHdlZW4gdGhlIHNpbXBsZXIgYW5kIG1vcmUgY29tcGxlcyB2ZXJzaW9ucyBvZiB0aGUgbW9kZWwuIEV2ZW4NCmJldHRlciwgdXNlIHRoZSBgYW5vdmEoKWAgZnVuY3Rpb24gdG8gY29tcGFyZSB0aGUgbW9kZWxzLjwvcT4NCg0KYGBge3J9DQphbm92YSgNCiAgICBsbShyaW5+U3luYXBzZXMsIGRhdGE9YXMuZGF0YS5mcmFtZShjb2xEYXRhKGRkcykpKSwgDQogICAgbG0ocmluflN5bmFwc2VzK05ldXJvbiwgZGF0YT1hcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKSkpDQopDQphbm92YSgNCiAgICBsbShyaW5+U3luYXBzZXMsIGRhdGE9YXMuZGF0YS5mcmFtZShjb2xEYXRhKGRkcykpKSwgDQogICAgbG0ocmluflN5bmFwc2VzK29yaWdpbiwgZGF0YT1hcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKSkpDQopDQphbm92YSgNCiAgICBsbShyaW5+TmV1cm9uLCBkYXRhPWFzLmRhdGEuZnJhbWUoY29sRGF0YShkZHMpKSksDQogICAgbG0ocmlufk5ldXJvbitvcmlnaW4sIGRhdGE9YXMuZGF0YS5mcmFtZShjb2xEYXRhKGRkcykpKQ0KKQ0KYW5vdmEoDQogICAgbG0ocmluflN5bmFwc2VzK29yaWdpbiwgZGF0YT1hcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKSkpLA0KICAgIGxtKHJpbn5TeW5hcHNlcytOZXVyb24rb3JpZ2luLCBkYXRhPWFzLmRhdGEuZnJhbWUoY29sRGF0YShkZHMpKSkNCikNCmxtLmZpbmFsIDwtIGxtKHJpbn5TeW5hcHNlcytOZXVyb24sIGRhdGE9YXMuZGF0YS5mcmFtZShjb2xEYXRhKGRkcykpKQ0KYGBgDQoNCllvdSBjYW4gYWxzbyBjaGVjayB0aGUgZGlhZ25vc3RpYyBwbG90cyBieSBzaW1wbHkgdXNpbmcgdGhlIGZ1bmN0aW9uIGBwbG90KClgDQpvbiB0aGUgbGluZWFyIGZpdC4gRG8geW91IHNlZSBhIHNhbXBsZSB0aGF0IHlvdSBtYXkgd2FudCB0byBleGNsdWRlIGJlZm9yZQ0KcnVubmluZyB5b3VyIG1vZGVsIGFnYWluPyBEb2VzIGl0IG1ha2UgYSBkaWZmZXJlbmNlPw0KDQpgYGB7ciwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NX0NCnBsb3QobG0uZmluYWwpDQpgYGANCg0KDQoNCg0KIyBEaWZmZXJlbnRpYWwgZ2VuZSBleHByZXNzaW9uDQoNCiMjIFJ1bm5pbmcgYW4gZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMNCg0KV2l0aCB0aGUgYERFU2VxMmAgcGFja2FnZSwgd2UgY2FuIHVzZSB0aGUgZnVuY3Rpb24gYERFU2VxKClgIG9uIG91ciBgZGRzYA0Kb2JqZWN0IHRvIHJ1biB0aGUgd2hvbGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gcGlwZWxpbmUsIHdoaWNoIHdpbGwgcnVuIHRocmVlDQpzdGVwcyBpbiBvcmRlcjoNCg0KMS4gYGVzdGltYXRlU2l6ZUZhY3RvcnMoZGRzKWA6IGVzdGltYXRpb24gb2Ygc2FtcGxlLXNwZWNpZmljIG5vcm1hbGl6YXRpb24gcGFyYW1ldGVycw0KMi4gYGVzdGltYXRlRGlzcGVyc2lvbnMoZGRzKWA6IGVzdGltYXRpb24gb2YgZ2VuZS1zcGVjaWZpYyBkaXNwZXJzaW9uIHBhcmFtZXRlcnMNCjMuIGBuYmlub21XYWxkVGVzdChkZHMpYDogbmVnYXRpdmUgYmlub21pYWwgZ2VuZXJhbGl6ZWQgbGluZWFyIG1vZGVsIHRvIGNhbGN1bGF0ZSB0aGUgZGVzaXJlZCBsb2cyLWZvbGQgY2hhbmdlcyBhbmQgY2FsY3VsYXRpb24gb2YgV2FsZCBzdGF0aXN0aWNzDQoNCldoZW4geW91IHJ1biBgREVTZXFgLCBieSBkZWZhdWx0IGl0IHdpbGwgY2FsY3VsYXRlIHRoZSBmaXQgYWNjb3JkaW5nIHRvDQp3aGF0ZXZlciBtb2RlbCB5b3UgaGFkIHNwZWNpZmllZCB3aGVuIHlvdSBjcmVhdGVkIHRoZSBvYmplY3QuIFRoZSBtb2RlbCdzDQpmb3JtdWxhIGNhbiBiZSBwcmludGVkIG9uIHNjcmVlbiB1c2luZyBgZGVzaWduKGRkcylgLg0KDQpMZXQncyBjaGFuZ2UgdGhlIG1vZGVsJ3MgZm9ybXVsYSBvZiB0aGUgb2JqZWN0IGxpa2Ugc286DQoNCiAgICBkZXNpZ24oZGRzKSA8LSB+IHNleCArIGFnZV95ZWFycyArIGNvbmRpdGlvbg0KDQphbmQgdGhlbiBydW4gYERFU2VxKClgIG9uIHRoZSBgZGRzYCBvYmplY3QuDQoNCioqTm90ZSAxOioqIGlmIHlvdSBoYXZlIG1hbnVhbGx5IGNoYW5nZWQgc29tZXRoaW5nIGluIHRoZSBgY29sRGF0YShkZHMpYCwgaXQgbWF5DQpiZSB0aGF0IHNvbWUgY29sdW1ucyBhcmUgb2YgdHlwZSAiY2hhcmFjdGVyIi4gVGhleSBzaG91bGQgYmUgY29udmVydGVkIHRvDQpmYWN0b3JzLg0KDQoqKk5vdGUgMjoqKiB0byBhdm9pZCBzb21lIGhlYWRhY2hlcyBsYXRlciBvbiwgd2Ugd2FudCB0byBiZSB2ZXJ5IGV4cGxpY2l0IHdpdGgNCm91ciB2YXJpYWJsZSBvZiBpbnRlcmVzdCAodGhlIGRpc2Vhc2Ugc3RhdHVzKSBieSBzcGVjaWZ5aW5nIHdoaWNoIG9mIHRoZSB0d28NCmxldmVscyAoImNhc2UiIG9yICJjb250cm9sIikgaXMgdGhlIFJFRkVSRU5DRSB2YWx1ZS4gVGhpcyBjYW4gYmUgZG9uZSBieSB1c2luZw0KdGhlIGByZWxldmVsKClgIGZ1bmN0aW9uIGxpa2Ugc286DQoNCiAgICBjb2xEYXRhKGRkcykkY29uZGl0aW9uIDwtIHJlbGV2ZWwoY29sRGF0YShkZHMpJGNvbmRpdGlvbiwgcmVmPSJDb250cm9sIikNCg0KPHE+UnVuIGEgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgb24gdGhlIGBkZHNgIG9iamVjdCB1c2luZyB0aGUNCmBERVNlcSgpYCBmdW5jdGlvbi4gVGhlIG1vZGVsJ3MgZm9ybXVsYSBzaG91bGQgYmUgX19+IHNleCArIGFnZSArIGNvbmRpdGlvbl9fLg0KT3ZlcndyaXRlIHdpdGggdGhlIHJldHVybmVkIG9iamVjdCB0aGUgYGRkc2Agb2JqZWN0LjwvcT4NCg0KVXNlZnVsIGZ1bmN0aW9uczoNCg0KKiBgbXV0YXRlX2lmKClgDQoqIGBpcy5jaGFyYWN0ZXIoKWANCiogYERFU2VxKClgDQoNCmBgYHtyfQ0KIyBjb252ZXJ0IHRvIGNoYXIgdG8gZmFjdG9yDQpjaHJfY29scyA8LSBzYXBwbHkoY29sRGF0YShkZHMpLCBpcy5jaGFyYWN0ZXIpDQpjb2xEYXRhKGRkcylbY2hyX2NvbHNdIDwtIGxhcHBseShjb2xEYXRhKGRkcylbY2hyX2NvbHNdLCBhcy5mYWN0b3IpDQoNCiMgcmVsZXZlbCBjb25kaXRpb24ganVzdCBpbiBjYXNlDQpjb2xEYXRhKGRkcykkY29uZGl0aW9uIDwtIHJlbGV2ZWwoY29sRGF0YShkZHMpJGNvbmRpdGlvbiwgcmVmPSJDb250cm9sIikNCg0KZGVzaWduKGRkcykgPC0gfiBzZXggKyBhZ2VfeWVhcnMgKyBjb25kaXRpb24NCmRkcyA8LSBERVNlcShkZHMpDQpgYGANCg0KIyMgTG9va2luZyBhdCB0aGUgcmVzdWx0cw0KDQpUaGUgYHJlc3VsdHMoKWAgZnVuY3Rpb24gZXh0cmFjdHMgdGhlIGxvZzIgZm9sZCBjaGFuZ2VzIGFuZCBwIHZhbHVlcyBmcm9tIHRoZQ0KYGRkc2Agb2JqZWN0IChhcyBsb25nIGFzIHRoZSBgREVTZXFgIGZ1bmN0aW9uIHdhcyBydW4pLiBCeSBkZWZhdWx0LCB0aGUNCmZ1bmN0aW9uIHdpbGwgZXh0cmFjdCB0aGUgbG9nMiBmb2xkIGNoYW5nZSAqKmZvciB0aGUgbGFzdCB2YXJpYWJsZSBpbiB0aGUNCmRlc2lnbiBmb3JtdWxhKiogKGluIG91ciBjYXNlLCB0aGUgZGlzZWFzZSBzdGF0dXMpLiBIb3dldmVyLCBpdCBpcyBhbHdheXMgZ29vZA0KcHJhY3RpY2UgdG8gZXhwbGljaXRseSBzcGVjaWZ5IHRoZSBjb250cmFzdCB3aGVuIGNhbGxpbmcgYHJlc3VsdHMoKWAgd2l0aCB0aGUNCiJjb250cmFzdCIgb3IgIm5hbWUiIGFyZ3VtZW50LiBUaGVyZSBhcmUgZGlmZmVyZW50IHdheXMgb2Ygc3BlY2lmeWluZyBjb250cmFzdHMNCmJ1dCwgZm9yIGV4YW1wbGUsIGlmIHdlIHdhbnRlZCB0byBleHRyYWN0IHRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiByZXN1bHRzDQpiZXR3ZWVuIG1hbGVzIGFuZCBmZW1hbGVzLCB3ZSBjb3VsZCB1c2U6DQoNCiAgICByZXN1bHRzKGRkcywgbmFtZT0ic2V4X01fdnNfRiIpDQoNCm9yDQoNCiAgICByZXN1bHRzKGRkcywgY29udHJhc3Q9Yygic2V4IiwgIk0iLCAiRiIpKQ0KDQp0aGV5IGFyZSBlcXVpdmFsZW50LiBBIG5pY2UgdHJpY2sgdG8gc2VlIHdoaWNoIGNvbnRyYXN0cyBjYW4gYmUgZXh0cmFjdGVkIHVzaW5nDQoibmFtZT0iLCB5b3UgY2FuIHVzZQ0KDQogICAgcmVzdWx0c05hbWVzKGRkcykNCg0Kd2hpY2ggcHJpbnRzIGFsbCBwb3NzaWJsZSB2YWx1ZXMgZm9yIHRoZSBjb250cmFzdCBuYW1lLiBJbiBvdXIgY2FzZSwgdGhlDQpwb3NzaWJsZSB2YWx1ZXMgZm9yIHRoZSAibmFtZT0iIGFyZ3VtZW50IGFyZTogKioiSW50ZXJjZXB0IiwgInNleF9NX3ZzX0YiLA0KImFnZV95ZWFycyIsIGFuZCAiY29uZGl0aW9uX0Nhc2VfdnNfQ29udHJvbCIqKg0KDQo8cT5Vc2UgdGhlIGByZXN1bHRzKClgIGZ1bmN0aW9uIG9uIHRoZSBgZGRzYCBvYmplY3QgdG8gZXh0cmFjdCB0aGUgZGlmZmVyZW50aWFsDQpleHByZXNzaW9uIGJldHdlZW4gY2FzZXMgYW5kIGNvbnRyb2xzIGFuZCBmaW5kIG91dCB3aGV0aGVyIGFueSBnZW5lcyBhcmUNCnNpZ25pZmljYW50IGFmdGVyIGNvcnJlY3Rpb24uPC9xPg0KDQpgYGB7cn0NCiNyZXN1bHRzKGRkcywgbmFtZT0ic2V4X01fdnNfRiIpDQojcmVzdWx0cyhkZHMsIGNvbnRyYXN0PWMoInNleCIsICJNIiwgIkYiKSkNCnJlcyA8LSByZXN1bHRzKGRkcywgbmFtZT0iY29uZGl0aW9uX0Nhc2VfdnNfQ29udHJvbCIpICU+JSBhc190aWJibGUocm93bmFtZXM9ImdlbmVfbmFtZSIpDQpyZXMgJT4lIGFycmFuZ2UocGFkaikNCmBgYA0KDQpBcmUgdGhlcmUgYW55IHNpZ25pZmljYW50IGNoYW5nZXMgaW4gZ2VuZSBleHByZXNzaW9uIGFzc29jaWF0ZWQgd2l0aCB0aGUNCmRpc2Vhc2U/IA0KDQpXZSBoYXZlIHNlZW4gdGhhdCBSSU4gaXMgYXNzb2NpYXRlZCB3aXRoIHRoZSBtYWluIGxpbmVzIG9mIHZhcmlhdGlvbiBpbiBnZW5lDQpleHByZXNzaW9uLiBXZSBzaG91bGQgZXhwZWN0LCB0aGVuLCB0aGF0IFJJTiBpcyBhZGRpbmcgYSBsb3Qgb2YgInVuaW50ZXJlc3RpbmciDQp2YXJpYXRpb24gdG8gdGhlIGRhdGFzZXQsIGkuZS4sIGEgbG90IG9mIG5vaXNlLiBXaGF0IGhhcHBlbnMgaWYgeW91IGFkZCAicmluIg0KYXMgYSBjb3ZhcmlhdGU/DQoNCllvdSdsbCBuZWVkIHRvIHVwZGF0ZSB0aGUgZm9ybXVsYSBhbmQgcmUtcnVuIHRoZSB3aG9sZSBwaXBlbGluZSBsaWtlIHNvOg0KDQpgYGANCmRlc2lnbihkZHMpIDwtIH4gc2V4ICsgYWdlX3llYXJzICsgcmluICsgY29uZGl0aW9uDQpkZHMgPC0gREVTZXEoZGRzKQ0KcmVzIDwtIHJlc3VsdHMoZGRzLCBuYW1lPSJjb25kaXRpb25fQ2FzZV92c19Db250cm9sIikgJT4lIGFzX3RpYmJsZShyb3duYW1lcz0iZ2VuZV9uYW1lIikNCnJlcyAlPiUgYXJyYW5nZShwYWRqKQ0KYGBgDQoNCjxxPlJlLXJ1biB0aGUgYW5hbHlzZXMgd2l0aCBSSU4gYXMgYSBjb3ZhcmlhdGUuPC9xPg0KDQpgYGB7cn0NCmRkc19yaW4gPC0gZGRzDQpkZXNpZ24oZGRzX3JpbikgPC0gfiBzZXggKyBhZ2VfeWVhcnMgKyByaW4gKyBjb25kaXRpb24NCmRkc19yaW4gPC0gREVTZXEoZGRzX3JpbikNCnJlc19yaW4gPC0gcmVzdWx0cyhkZHNfcmluLCBuYW1lPSJjb25kaXRpb25fQ2FzZV92c19Db250cm9sIikgJT4lIGFzX3RpYmJsZShyb3duYW1lcz0iZ2VuZV9uYW1lIikNCnJlc19yaW4gJT4lIGFycmFuZ2UocGFkaikNCmBgYA0KDQpXaGF0IGRvIHlvdSB0aGluayBoYXMgY2hhbmdlZD8gSG93IGRvZXMgdGhlIGV4dHJhIGNvdmFyaWF0ZSBhZGRlZCBpbmZsdWVuY2UgdGhlDQpyZXN1bHRzPyBXaGF0IGRvIHlvdSB0aGluayB3b3VsZCBoYXZlIGhhcHBlbmVkIGlmIHlvdSBhZGRlZCB0byB0aGUgbW9kZWwgYQ0KY292YXJpYXRlIHRoYXQgd2FzIGhpZ2hseSBhc3NvY2lhdGVkIHdpdGggdGhlIGNvbmRpdGlvbiBvZiBpbnRlcmVzdCAoaS5lLiwNCiJjb25mb3VuZGVkIik/DQoNCg0KU2luY2UgdGhlIGNvaG9ydHMgaGFkIGRpZmZlcmVudCBSSU5zIGFuZCBwb3N0LW1vcnRlbSBpbnRlcnZhbHMsIHRyeSB0byBydW4gdGhlDQpjb2hvcnRzIHNlcGFyYXRlbHkgYW5kIGV4cGxvcmUgdGhlIHJlc3VsdHMuDQoNCjxxPlJ1biB0aGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzZXMgc2VwYXJhdGVseSBmb3IgZWFjaCBjb2hvcnQuPC9xPg0KDQpgYGB7cn0NCmRkc19wdyA8LSBkZHNbLGNvbERhdGEoZGRzKSRvcmlnaW49PSJQVyJdDQpkZHNfcHcgPC0gREVTZXEoZGRzX3B3KQ0KcmVzdWx0cyhkZHNfcHcpICU+JSBhc190aWJibGUocm93bmFtZXM9ImdlbmVfbmFtZSIpICU+JSBhcnJhbmdlKHBhZGopDQpkZHNfbmJiIDwtIGRkc1ssY29sRGF0YShkZHMpJG9yaWdpbj09Ik5CQiJdDQpkZHNfbmJiIDwtIERFU2VxKGRkc19uYmIpDQpyZXN1bHRzKGRkc19uYmIpICU+JSBhc190aWJibGUocm93bmFtZXM9ImdlbmVfbmFtZSIpICU+JSBhcnJhbmdlKHBhZGopDQpgYGANCg0KSG93IGNhbiB0d28gY29ob3J0cyBleGhpYml0IHN1Y2ggZGlmZmVyZW5jZXMgaW4gdGhlaXIgcmVzdWx0cz8gV2hhdCBkb2VzIHRoaXMNCnN1Z2dlc3QgYWJvdXQgaW50ZXItc3R1ZHkgcmVwbGljYWJpbGl0eT8NCg0KDQoNCiMgR2VuZSBzZXQgZW5yaWNobWVudCBhbmFseXNlDQoNCldlIGFyZSBnb2luZyB0byB1c2UgdGhlIGBmZ3NlYWAgcGFja2FnZSB0byBydW4gYSBnZW5lIHNldCBlbnJpY2htZW50IGFuYWx5c2lzDQpvbiB5b3VyIHJlc3VsdHMuDQoNCjxxPkluc3RhbGwgdGhlIGBmZ3NlYWAgcGFja2FnZSB1c2luZyBgQmlvY29uZHVjdG9yOjppbnN0YWxsKGZnc2VhKWAgYW5kIGxvYWQNCnRoZSBwYWNrYWdlLjwvcT4NCg0KYGBge3J9DQpyZXF1aXJlKCdmZ3NlYScpDQpgYGANCg0KV2UgY2FuIGRvd25sb2FkICJnbXQiIGZvcm1hdHRlZCBmaWxlcyBmcm9tDQpbTVNpZ0RCXShodHRwOi8vd3d3LmdzZWEtbXNpZ2RiLm9yZy9nc2VhL21zaWdkYi9jb2xsZWN0aW9ucy5qc3ApIHdlYnNpdGUsIHdoaWNoDQpjYW4gYmUgaW1wb3J0ZWQgaW50byBSIHVzaW5nIHRoZSBgZ210UGF0aHdheXMoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYGZnc2VhYA0KcGFja2FnZS4gSSBoYXZlIGFscmVhZHkgZG93bmxvYWRlZCB0aGUgS0VHRyBkYXRhYmFzZSwgd2hpY2ggY2FuIGJlIGxvYWRlZCBpbnRvDQphIHZhcmlhYmxlIGxpa2Ugc286DQoNCiAgICBLRUdHX3BhdGh3YXlzIDwtIGdtdFBhdGh3YXlzKCJjMi5jcC5rZWdnLnYyMDIyLjEuSHMuc3ltYm9scy5nbXQiKQ0KDQo8cT5Mb2FkIHRoZSBLRUdHIHBhdGh3YXkgbGlzdCB1c2luZyB0aGUgcHJldmlvdXMgY29tbWFuZC48L3E+DQoNCmBgYHtyfQ0KS0VHR19wYXRod2F5cyA8LSBnbXRQYXRod2F5cygiYzIuY3Aua2VnZy52MjAyMi4xLkhzLnN5bWJvbHMuZ210IikNCmBgYA0KDQpXZSBhcmUgZ29pbmcgdG8gcHJlcGFyZSBhbiBpbnB1dCBmb3IgdGhlIGBmZ3NlYSgpYCBmdW5jdGlvbi4gV2UgbmVlZCB0byByYW5rDQp0aGUgZ2VuZXMgYWNjb3JkaW5nIHRvIG91ciB2YWx1ZSBvZiBpbnRlcmVzdCAoaW4gb3VyIGNhc2UsIGl0IHdpbGwgYmUgdGhlDQoic3RhdCIgY29sdW1uIG9mIG91ciBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiByZXN1bHRzLCBhbHRob3VnaCB5b3UgY291bGQgdXNlDQotbG9nMTAocC12YWx1ZSkgb3IgbG9nMkZvbGRDaGFuZ2UpLg0KDQo8cT5DcmVhdGUgYSB2ZWN0b3Igd2l0aCB0aGUgInN0YXQiIHZhbHVlcyBmcm9tIHRoZSByZXN1bHRzIGFuZCBuYW1lIHRoZSB2YWx1ZXMNCndpdGggdGhlIGNvcnJlc3BvbmRpbmcgZ2VuZSBuYW1lcy48cT4NCg0KYGBge3J9DQpyYW5rcyA8LSByZXMkc3RhdA0KbmFtZXMocmFua3MpIDwtIHJlcyRnZW5lX25hbWUNCmBgYA0KDQo8cT5SdW4gYGdzZWFfcmVzIDwtIGZnc2VhKEtFR0dfcGF0aHdheXMsIHJhbmtzKWAgYW5kIGV4cGxvcmUgdGhlIHJlc3VsdHMNCigicmFua3MiIGlzIHRoZSB2ZWN0b3Igb2YgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gc3RhdHMpLjwvcT4NCg0KYGBge3J9DQpnc2VhX3JlcyA8LSBmZ3NlYShLRUdHX3BhdGh3YXlzLCByYW5rcykgJT4lIGFycmFuZ2UocGFkaikNCmdzZWFfcmVzDQpgYGANCg0KV2hhdCBpcyBhdCB0aGUgdG9wPyBDYW4geW91IHJ1biB0aGUgR1NFQSB3aXRoIHRoZSByZXN1bHRzIGZyb20gdGhlIHR3bw0KYWx0ZXJuYXRlIG1vZGVscyAoaS5lLiwgd2l0aCBhbmQgd2l0aG91dGggYWNjb3VudGluZyBmb3IgdGhlIGVmZmVjdCBvZiBSSU4pPw0KDQo8cT5DcmVhdGUgYSBuZXcgdmVjdG9yIHdpdGggdGhlIHN0YXRzIG9mIHRoZSBhcHByb3ByaWF0ZSByZXN1bHRzIGFuZCBydW4NCmBmZ3NlYWAgYWdhaW4uPHE+DQoNCmBgYHtyfQ0KcmFua3NfcmluIDwtIHJlc19yaW4kc3RhdA0KbmFtZXMocmFua3NfcmluKSA8LSByZXNfcmluJGdlbmVfbmFtZQ0KZ3NlYV9yZXNfcmluIDwtIGZnc2VhKEtFR0dfcGF0aHdheXMsIHJhbmtzX3JpbikgJT4lIGFycmFuZ2UocGFkaikNCmdzZWFfcmVzX3Jpbg0KYGBgDQoNCldoYXQgaGFwcGVucyBpZiB5b3UgYWRkIE5ldXJvbiBvciBTeW5hcHNlcyB0byB0aGUgZGVzaWduIG1vZGVsPyBIb3cgdGhhdCBkb2VzDQphZmZlY3QgdGhlIEdTRUEgcmVzdWx0cz8NCg0KPHE+UnVuIGFub3RoZXIgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgdXNpbmcgTmV1cm9uIGFuZC9vciBTeW5hcHNlcyBhcw0KY292YXJpYXRlcyBhbmQgZXhwbG9yZSB0aGUgZGlmZmVyZW5jZXMgaW4gdGhlIGVucmljaGVkIHBhdGh3YXlzLjxxPg0KDQpgYGB7cn0NCmRkc19uZXUgPC0gZGRzDQpkZXNpZ24oZGRzX25ldSkgPC0gfiBzZXggKyBhZ2VfeWVhcnMgKyByaW4gKyBOZXVyb24gKyBTeW5hcHNlcyArIGNvbmRpdGlvbg0KZGRzX25ldSA8LSBERVNlcShkZHNfbmV1KQ0KcmVzX25ldSA8LSByZXN1bHRzKGRkc19uZXUpICU+JSBhc190aWJibGUocm93bmFtZXM9ImdlbmVfbmFtZSIpDQpyYW5rc19uZXUgPC0gcmVzX25ldSRzdGF0DQpuYW1lcyhyYW5rc19uZXUpIDwtIHJlc19uZXUkZ2VuZV9uYW1lDQpnc2VhX3Jlc19uZXUgPC0gZmdzZWEoS0VHR19wYXRod2F5cywgcmFua3NfbmV1KSAlPiUgYXJyYW5nZShwYWRqKQ0KZ3NlYV9yZXNfbmV1DQpgYGANCg0KDQpDYW4geW91IHRoaW5rIG9mIGEgd2F5IHRvIGlkZW50aWZ5IHRoZSBwYXRod2F5cyB0aGF0IGFyZSBtb3JlIHN1c2NlcHRpYmxlIHRvDQpSSU4gYW5kL29yIE5ldXJvbiBjb3JyZWN0aW9uPw0KDQoNCg==